[ovs-dev] [PATCHv6 12/14] dpif: Index flows using unique identifiers.

Joe Stringer joestringer at nicira.com
Fri Sep 26 09:28:16 UTC 2014


This patch modifies the dpif interface to allow flows to be manipulated
using a 128-bit identifier. This allows revalidator threads to perform
datapath operations faster, as they do not need to serialise the entire
flow key for operations like flow_get and flow_delete. In conjunction
with a future patch to simplify the dump interface, this provides a
significant performance benefit for revalidation.

When handlers assemble flow_put operations, they specify a unique
identifier (UID) for each flow as it is passed down to the datapath to
be stored with the flow. The UID is currently provided to handlers
by the dpif during upcall processing.

When revalidators assemble flow_get or flow_del operations, they specify
the UID for the flow along with the key, and a boolean for whether to
send the request using only a UID or to send the request using the UID
and flow key. The former is preferred for newer datapaths that support
UID, while the latter is used for backwards compatibility.

Signed-off-by: Joe Stringer <joestringer at nicira.com>
---
v6: Split out from "dpif: Add Unique flow identifiers."
    Make sure that actions are fetched if terse=false.
    Add additional dpif.h documentation to describe the interface.
    Rebase.
v5: Always pass flow_key down to dpif, to improve error logging.
    Fix extraneous netflow_unref.
    Fix testsuite failure.
    Rebase.
v4: Skip sending flow key in flow_del.
    Fix race conditions with tests.
    Combine dpif,upcall,dpif-netdev,dpif-linux changes into one patch.
    Log UID in flow_del/flow_get/flow_dump messages.
v3: Rebase.
---
 lib/dpctl.c                   |   10 +++--
 lib/dpif-netdev.c             |   66 +++++++++++++++------------
 lib/dpif-netlink.c            |  100 +++++++++++++++++++++++++++++++++--------
 lib/dpif.c                    |   34 +++++++++-----
 lib/dpif.h                    |   37 ++++++++++++---
 lib/odp-util.c                |   54 ++++++++++++++++++++++
 lib/odp-util.h                |   25 +++++++++++
 ofproto/ofproto-dpif-upcall.c |   13 +++++-
 ofproto/ofproto-dpif.c        |   15 +++++--
 tests/dpif-netdev.at          |    1 +
 tests/ofproto-dpif.at         |   24 +++++-----
 tests/ofproto-macros.at       |    1 +
 12 files changed, 296 insertions(+), 84 deletions(-)

diff --git a/lib/dpctl.c b/lib/dpctl.c
index 623f5b1..80f6ab1 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -799,6 +799,7 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
     struct ofpbuf actions;
     struct ofpbuf key;
     struct ofpbuf mask;
+    ovs_u128 uid;
     struct dpif *dpif;
     char *dp_name;
     struct simap port_names;
@@ -836,12 +837,13 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
         dpctl_error(dpctl_p, error, "parsing actions");
         goto out_freeactions;
     }
+    dpif_flow_hash(dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_put(dpif, flags,
                           ofpbuf_data(&key), ofpbuf_size(&key),
                           ofpbuf_size(&mask) == 0 ? NULL : ofpbuf_data(&mask),
                           ofpbuf_size(&mask),
                           ofpbuf_data(&actions), ofpbuf_size(&actions),
-                          dpctl_p->print_statistics ? &stats : NULL);
+                          &uid, dpctl_p->print_statistics ? &stats : NULL);
     if (error) {
         dpctl_error(dpctl_p, error, "updating flow table");
         goto out_freeactions;
@@ -896,6 +898,7 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     struct dpif_port_dump port_dump;
     struct ofpbuf key;
     struct ofpbuf mask; /* To be ignored. */
+    ovs_u128 uid;
     struct dpif *dpif;
     char *dp_name;
     struct simap port_names;
@@ -926,9 +929,10 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
         goto out;
     }
 
+    dpif_flow_hash(dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_del(dpif,
-                          ofpbuf_data(&key), ofpbuf_size(&key),
-                          dpctl_p->print_statistics ? &stats : NULL);
+                          ofpbuf_data(&key), ofpbuf_size(&key), &uid,
+                          dpctl_p->print_statistics ? &stats : NULL, false);
     if (error) {
         dpctl_error(dpctl_p, error, "deleting flow");
         goto out;
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 306a84e..a8a8f9e 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -285,8 +285,9 @@ struct dp_netdev_flow {
     /* Packet classification. */
     const struct cls_rule cr;   /* In owning dp_netdev's 'cls'. */
 
-    /* Hash table index by unmasked flow. */
+    /* Hash table index by uid. */
     const struct cmap_node node; /* In owning dp_netdev's 'flow_table'. */
+    const ovs_u128 uid;          /* Unique flow identifier. */
     const struct flow flow;      /* The flow that created this entry. */
 
     /* Number of references.
@@ -1096,6 +1097,12 @@ static void dp_netdev_flow_unref(struct dp_netdev_flow *flow)
     }
 }
 
+static uint32_t
+flow_uid_hash(const ovs_u128 *uid)
+{
+    return uid->u32[0];
+}
+
 static void
 dp_netdev_remove_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
     OVS_REQUIRES(dp->flow_mutex)
@@ -1104,7 +1111,7 @@ dp_netdev_remove_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
     struct cmap_node *node = CONST_CAST(struct cmap_node *, &flow->node);
 
     classifier_remove(&dp->cls, cr);
-    cmap_remove(&dp->flow_table, node, flow_hash(&flow->flow, 0));
+    cmap_remove(&dp->flow_table, node, flow_uid_hash(&flow->uid));
     flow->dead = true;
 
     dp_netdev_flow_unref(flow);
@@ -1375,13 +1382,13 @@ dp_netdev_lookup_flow(const struct dp_netdev *dp, const struct miniflow *key)
 }
 
 static struct dp_netdev_flow *
-dp_netdev_find_flow(const struct dp_netdev *dp, const struct flow *flow)
+dp_netdev_find_flow(const struct dp_netdev *dp, const ovs_u128 *uid)
 {
     struct dp_netdev_flow *netdev_flow;
 
-    CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_hash(flow, 0),
+    CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_uid_hash(uid),
                              &dp->flow_table) {
-        if (flow_equal(&netdev_flow->flow, flow)) {
+        if (!memcmp(&netdev_flow->uid, uid, sizeof *uid)) {
             return netdev_flow;
         }
     }
@@ -1412,8 +1419,7 @@ get_dpif_flow_stats(const struct dp_netdev_flow *netdev_flow,
  * 'mask_buf'. Actions will be returned without copying, by relying on RCU to
  * protect them. */
 static void
-dp_netdev_flow_to_dpif_flow(const struct dpif *dpif,
-                            const struct dp_netdev_flow *netdev_flow,
+dp_netdev_flow_to_dpif_flow(const struct dp_netdev_flow *netdev_flow,
                             struct ofpbuf *key_buf, struct ofpbuf *mask_buf,
                             struct dpif_flow *flow)
 {
@@ -1443,8 +1449,7 @@ dp_netdev_flow_to_dpif_flow(const struct dpif *dpif,
     flow->actions = actions->actions;
     flow->actions_len = actions->size;
 
-    dpif_flow_hash(dpif, &netdev_flow->flow, sizeof netdev_flow->flow,
-                   &flow->uid);
+    memcpy(&flow->uid, &netdev_flow->uid, sizeof flow->uid);
     get_dpif_flow_stats(netdev_flow, &flow->stats);
 }
 
@@ -1545,19 +1550,16 @@ dpif_netdev_flow_get(const struct dpif *dpif, const struct dpif_flow_get *get)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
     struct dp_netdev_flow *netdev_flow;
-    struct flow key;
-    int error;
+    int error = 0;
 
-    error = dpif_netdev_flow_from_nlattrs(get->key, get->key_len, &key);
-    if (error) {
-        return error;
+    if (!get->uid) {
+        return EINVAL;
     }
 
-    netdev_flow = dp_netdev_find_flow(dp, &key);
-
+    netdev_flow = dp_netdev_find_flow(dp, get->uid);
     if (netdev_flow) {
-        dp_netdev_flow_to_dpif_flow(dpif, netdev_flow, get->buffer,
-                                    get->buffer, get->flow);
+        dp_netdev_flow_to_dpif_flow(netdev_flow, get->buffer, get->buffer,
+                                    get->flow);
      } else {
         error = ENOENT;
     }
@@ -1567,6 +1569,7 @@ dpif_netdev_flow_get(const struct dpif *dpif, const struct dpif_flow_get *get)
 
 static int
 dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
+                   const ovs_u128 *uid,
                    const struct nlattr *actions, size_t actions_len)
     OVS_REQUIRES(dp->flow_mutex)
 {
@@ -1574,7 +1577,7 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
 
     netdev_flow = xzalloc(sizeof *netdev_flow);
     *CONST_CAST(struct flow *, &netdev_flow->flow) = match->flow;
-
+    *CONST_CAST(ovs_u128 *, &netdev_flow->uid) = *uid;
     ovs_refcount_init(&netdev_flow->ref_cnt);
 
     ovsthread_stats_init(&netdev_flow->stats);
@@ -1586,7 +1589,7 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
                   match, NETDEV_RULE_PRIORITY);
     cmap_insert(&dp->flow_table,
                 CONST_CAST(struct cmap_node *, &netdev_flow->node),
-                flow_hash(&match->flow, 0));
+                flow_uid_hash(uid));
     classifier_insert(&dp->cls,
                       CONST_CAST(struct cls_rule *, &netdev_flow->cr));
 
@@ -1594,6 +1597,8 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
         struct ds ds = DS_EMPTY_INITIALIZER;
 
         ds_put_cstr(&ds, "flow_add: ");
+        odp_format_uid(uid, &ds);
+        ds_put_cstr(&ds, " ");
         match_format(match, &ds, OFP_DEFAULT_PRIORITY);
         ds_put_cstr(&ds, ", actions:");
         format_odp_actions(&ds, actions, actions_len);
@@ -1643,6 +1648,10 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
     }
     miniflow_init(&miniflow, &match.flow);
 
+    if (!put->uid) {
+        return EINVAL;
+    }
+
     ovs_mutex_lock(&dp->flow_mutex);
     netdev_flow = dp_netdev_lookup_flow(dp, &miniflow);
     if (!netdev_flow) {
@@ -1651,7 +1660,7 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
                 if (put->stats) {
                     memset(put->stats, 0, sizeof *put->stats);
                 }
-                error = dp_netdev_flow_add(dp, &match, put->actions,
+                error = dp_netdev_flow_add(dp, &match, put->uid, put->actions,
                                            put->actions_len);
             } else {
                 error = EFBIG;
@@ -1697,16 +1706,14 @@ dpif_netdev_flow_del(struct dpif *dpif, const struct dpif_flow_del *del)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
     struct dp_netdev_flow *netdev_flow;
-    struct flow key;
-    int error;
+    int error = 0;
 
-    error = dpif_netdev_flow_from_nlattrs(del->key, del->key_len, &key);
-    if (error) {
-        return error;
+    if (!del->uid) {
+        return EINVAL;
     }
 
     ovs_mutex_lock(&dp->flow_mutex);
-    netdev_flow = dp_netdev_find_flow(dp, &key);
+    netdev_flow = dp_netdev_find_flow(dp, del->uid);
     if (netdev_flow) {
         if (del->stats) {
             get_dpif_flow_stats(netdev_flow, del->stats);
@@ -1830,7 +1837,7 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread *thread_,
 
         ofpbuf_use_stack(&key, keybuf, sizeof *keybuf);
         ofpbuf_use_stack(&mask, maskbuf, sizeof *maskbuf);
-        dp_netdev_flow_to_dpif_flow(&dpif->dpif, netdev_flow, &key, &mask, f);
+        dp_netdev_flow_to_dpif_flow(netdev_flow, &key, &mask, f);
     }
 
     return n_flows;
@@ -2698,7 +2705,8 @@ fast_path_processing(struct dp_netdev_pmd_thread *pmd,
              * reasonable. */
             if (OVS_LIKELY(error != ENOSPC)
                 && !dp_netdev_lookup_flow(dp, mfs[i])) {
-                dp_netdev_flow_add(dp, &match, ofpbuf_data(add_actions),
+                dp_netdev_flow_add(dp, &match, &uid,
+                                   ofpbuf_data(add_actions),
                                    ofpbuf_size(add_actions));
             }
             ovs_mutex_unlock(&dp->flow_mutex);
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index b19b36a..aa25673 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -111,10 +111,14 @@ struct dpif_netlink_flow {
     size_t mask_len;
     const struct nlattr *actions;       /* OVS_FLOW_ATTR_ACTIONS. */
     size_t actions_len;
+    const struct nlattr *uid;           /* OVS_FLOW_ATTR_UID. */
+    size_t uid_len;
     const struct ovs_flow_stats *stats; /* OVS_FLOW_ATTR_STATS. */
     const uint8_t *tcp_flags;           /* OVS_FLOW_ATTR_TCP_FLAGS. */
     const ovs_32aligned_u64 *used;      /* OVS_FLOW_ATTR_USED. */
     bool clear;                         /* OVS_FLOW_ATTR_CLEAR. */
+
+    struct odputil_uidbuf uid_buf;      /* Buffer to hold 'uid'. */
 };
 
 static void dpif_netlink_flow_init(struct dpif_netlink_flow *);
@@ -251,6 +255,7 @@ dpif_netlink_open(const struct dpif_class *class OVS_UNUSED, const char *name,
     dp_request.name = name;
     dp_request.user_features |= OVS_DP_F_UNALIGNED;
     dp_request.user_features |= OVS_DP_F_VPORT_PIDS;
+    dp_request.user_features |= OVS_DP_F_INDEX_BY_UID;
     error = dpif_netlink_dp_transact(&dp_request, &dp, &buf);
     if (error) {
         return error;
@@ -1082,25 +1087,60 @@ dpif_netlink_port_poll_wait(const struct dpif *dpif_)
 }
 
 static void
+dpif_netlink_init_uid(const ovs_u128 *uid, struct odputil_uidbuf *uidbuf,
+                      bool terse, const struct nlattr **nla, size_t *nla_len)
+{
+    static const uint32_t flags = OVS_UID_F_SKIP_KEY | OVS_UID_F_SKIP_MASK
+                                  | OVS_UID_F_SKIP_ACTIONS;
+    struct ofpbuf buf;
+
+    ofpbuf_use_stack(&buf, uidbuf, sizeof *uidbuf);
+    odp_uid_to_nlattrs(&buf, uid, terse ? flags : 0);
+    *nla = ofpbuf_data(&buf);
+    *nla_len = ofpbuf_size(&buf);
+}
+
+static void
+dpif_netlink_init_flow_get__(const struct dpif_netlink *dpif,
+                             const struct nlattr *key, size_t key_len,
+                             const struct nlattr *uid, size_t uid_len,
+                             struct dpif_netlink_flow *request, bool terse)
+{
+    request->cmd = OVS_FLOW_CMD_GET;
+    request->dp_ifindex = dpif->dp_ifindex;
+    if (!terse) {
+        request->key = key;
+        request->key_len = key_len;
+    }
+    request->uid = uid;
+    request->uid_len = uid_len;
+}
+
+static void
 dpif_netlink_init_flow_get(const struct dpif_netlink *dpif,
-                           const struct nlattr *key, size_t key_len,
+                           const struct dpif_flow_get *get,
                            struct dpif_netlink_flow *request)
 {
+    const struct nlattr *uid;
+    size_t uid_len;
+
     dpif_netlink_flow_init(request);
-    request->cmd = OVS_FLOW_CMD_GET;
-    request->dp_ifindex = dpif->dp_ifindex;
-    request->key = key;
-    request->key_len = key_len;
+    dpif_netlink_init_uid(get->uid, &request->uid_buf, false, &uid, &uid_len);
+    dpif_netlink_init_flow_get__(dpif, get->key, get->key_len, uid, uid_len,
+                                 request, get->terse);
 }
 
 static int
 dpif_netlink_flow_get(const struct dpif_netlink *dpif,
-                      const struct nlattr *key, size_t key_len,
+                      const struct dpif_netlink_flow *flow,
                       struct dpif_netlink_flow *reply, struct ofpbuf **bufp)
 {
     struct dpif_netlink_flow request;
 
-    dpif_netlink_init_flow_get(dpif, key, key_len, &request);
+    dpif_netlink_flow_init(&request);
+    dpif_netlink_init_flow_get__(dpif, flow->key, flow->key_len,
+                                 flow->uid, flow->uid_len, &request,
+                                 false);
     return dpif_netlink_flow_transact(&request, reply, bufp);
 }
 
@@ -1119,6 +1159,9 @@ dpif_netlink_init_flow_put(struct dpif_netlink *dpif,
     request->key_len = put->key_len;
     request->mask = put->mask;
     request->mask_len = put->mask_len;
+    dpif_netlink_init_uid(put->uid, &request->uid_buf, false,
+                          &request->uid, &request->uid_len);
+
     /* Ensure that OVS_FLOW_ATTR_ACTIONS will always be included. */
     request->actions = (put->actions
                         ? put->actions
@@ -1138,8 +1181,12 @@ dpif_netlink_init_flow_del(struct dpif_netlink *dpif,
     dpif_netlink_flow_init(request);
     request->cmd = OVS_FLOW_CMD_DEL;
     request->dp_ifindex = dpif->dp_ifindex;
-    request->key = del->key;
-    request->key_len = del->key_len;
+    if (!del->terse) {
+        request->key = del->key;
+        request->key_len = del->key_len;
+    }
+    dpif_netlink_init_uid(del->uid, &request->uid_buf, del->terse,
+                          &request->uid, &request->uid_len);
 }
 
 struct dpif_netlink_flow_dump {
@@ -1242,8 +1289,13 @@ dpif_netlink_flow_to_dpif_flow(struct dpif *dpif, struct dpif_flow *dpif_flow,
     dpif_flow->mask_len = datapath_flow->mask_len;
     dpif_flow->actions = datapath_flow->actions;
     dpif_flow->actions_len = datapath_flow->actions_len;
-    dpif_flow_hash(dpif, datapath_flow->key, datapath_flow->key_len,
-                   &dpif_flow->uid);
+    if (datapath_flow->uid_len) {
+        odp_uid_from_nlattrs(datapath_flow->uid, datapath_flow->uid_len,
+                             &dpif_flow->uid, NULL);
+    } else {
+        dpif_flow_hash(dpif, datapath_flow->key, datapath_flow->key_len,
+                       &dpif_flow->uid);
+    }
     dpif_netlink_flow_get_stats(datapath_flow, &dpif_flow->stats);
 }
 
@@ -1286,8 +1338,7 @@ dpif_netlink_flow_dump_next(struct dpif_flow_dump_thread *thread_,
         } else {
             /* Rare case: the flow does not include actions.  Retrieve this
              * individual flow again to get the actions. */
-            error = dpif_netlink_flow_get(dpif, datapath_flow.key,
-                                          datapath_flow.key_len,
+            error = dpif_netlink_flow_get(dpif, &datapath_flow,
                                           &datapath_flow, &thread->nl_actions);
             if (error == ENOENT) {
                 VLOG_DBG("dumped flow disappeared on get");
@@ -1404,7 +1455,7 @@ dpif_netlink_operate__(struct dpif_netlink *dpif,
 
         case DPIF_OP_FLOW_GET:
             get = &op->u.flow_get;
-            dpif_netlink_init_flow_get(dpif, get->key, get->key_len, &flow);
+            dpif_netlink_init_flow_get(dpif, get, &flow);
             aux->txn.reply = get->buffer;
             dpif_netlink_flow_to_ofpbuf(&flow, &aux->request);
             break;
@@ -2325,14 +2376,15 @@ static int
 dpif_netlink_flow_from_ofpbuf(struct dpif_netlink_flow *flow,
                               const struct ofpbuf *buf)
 {
-    static const struct nl_policy ovs_flow_policy[] = {
-        [OVS_FLOW_ATTR_KEY] = { .type = NL_A_NESTED },
+    static const struct nl_policy ovs_flow_policy[__OVS_FLOW_ATTR_MAX] = {
+        [OVS_FLOW_ATTR_KEY] = { .type = NL_A_NESTED, .optional = true },
         [OVS_FLOW_ATTR_MASK] = { .type = NL_A_NESTED, .optional = true },
         [OVS_FLOW_ATTR_ACTIONS] = { .type = NL_A_NESTED, .optional = true },
         [OVS_FLOW_ATTR_STATS] = { NL_POLICY_FOR(struct ovs_flow_stats),
                                   .optional = true },
         [OVS_FLOW_ATTR_TCP_FLAGS] = { .type = NL_A_U8, .optional = true },
         [OVS_FLOW_ATTR_USED] = { .type = NL_A_U64, .optional = true },
+        [OVS_FLOW_ATTR_UID] = { .type = NL_A_NESTED, .optional = true },
         /* The kernel never uses OVS_FLOW_ATTR_CLEAR. */
     };
 
@@ -2354,12 +2406,21 @@ dpif_netlink_flow_from_ofpbuf(struct dpif_netlink_flow *flow,
                             ARRAY_SIZE(ovs_flow_policy))) {
         return EINVAL;
     }
+    if (!a[OVS_FLOW_ATTR_KEY] && !a[OVS_FLOW_ATTR_UID]) {
+        return EINVAL;
+    }
 
     flow->nlmsg_flags = nlmsg->nlmsg_flags;
     flow->dp_ifindex = ovs_header->dp_ifindex;
-    flow->key = nl_attr_get(a[OVS_FLOW_ATTR_KEY]);
-    flow->key_len = nl_attr_get_size(a[OVS_FLOW_ATTR_KEY]);
+    if (a[OVS_FLOW_ATTR_KEY]) {
+        flow->key = nl_attr_get(a[OVS_FLOW_ATTR_KEY]);
+        flow->key_len = nl_attr_get_size(a[OVS_FLOW_ATTR_KEY]);
+    }
 
+    if (a[OVS_FLOW_ATTR_UID]) {
+        flow->uid = nl_attr_get(a[OVS_FLOW_ATTR_UID]);
+        flow->uid_len = nl_attr_get_size(a[OVS_FLOW_ATTR_UID]);
+    }
     if (a[OVS_FLOW_ATTR_MASK]) {
         flow->mask = nl_attr_get(a[OVS_FLOW_ATTR_MASK]);
         flow->mask_len = nl_attr_get_size(a[OVS_FLOW_ATTR_MASK]);
@@ -2403,6 +2464,9 @@ dpif_netlink_flow_to_ofpbuf(const struct dpif_netlink_flow *flow,
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_MASK, flow->mask, flow->mask_len);
     }
 
+    if (flow->uid_len) {
+        nl_msg_put_unspec(buf, OVS_FLOW_ATTR_UID, flow->uid, flow->uid_len);
+    }
     if (flow->actions || flow->actions_len) {
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_ACTIONS,
                           flow->actions, flow->actions_len);
diff --git a/lib/dpif.c b/lib/dpif.c
index 3a2d611..54d1c18 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -87,6 +87,7 @@ static void log_flow_message(const struct dpif *dpif, int error,
                              const char *operation,
                              const struct nlattr *key, size_t key_len,
                              const struct nlattr *mask, size_t mask_len,
+                             const ovs_u128 *uid,
                              const struct dpif_flow_stats *stats,
                              const struct nlattr *actions, size_t actions_len);
 static void log_operation(const struct dpif *, const char *operation,
@@ -849,8 +850,8 @@ dpif_flow_flush(struct dpif *dpif)
 /* A dpif_operate() wrapper for performing a single DPIF_OP_FLOW_GET. */
 int
 dpif_flow_get(struct dpif *dpif,
-              const struct nlattr *key, size_t key_len,
-              struct ofpbuf *buf, struct dpif_flow *flow)
+              const struct nlattr *key, size_t key_len, const ovs_u128 *uid,
+              struct ofpbuf *buf, struct dpif_flow *flow, bool terse_get)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -858,10 +859,15 @@ dpif_flow_get(struct dpif *dpif,
     op.type = DPIF_OP_FLOW_GET;
     op.u.flow_get.key = key;
     op.u.flow_get.key_len = key_len;
+    op.u.flow_get.uid = uid;
+    op.u.flow_get.terse = terse_get;
     op.u.flow_get.buffer = buf;
+
+    memset(flow, 0, sizeof *flow);
     op.u.flow_get.flow = flow;
     op.u.flow_get.flow->key = key;
     op.u.flow_get.flow->key_len = key_len;
+    op.u.flow_get.flow->uid = *uid;
 
     opp = &op;
     dpif_operate(dpif, &opp, 1);
@@ -875,7 +881,7 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
               const struct nlattr *key, size_t key_len,
               const struct nlattr *mask, size_t mask_len,
               const struct nlattr *actions, size_t actions_len,
-              struct dpif_flow_stats *stats)
+              const ovs_u128 *uid, struct dpif_flow_stats *stats)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -888,6 +894,7 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
     op.u.flow_put.mask_len = mask_len;
     op.u.flow_put.actions = actions;
     op.u.flow_put.actions_len = actions_len;
+    op.u.flow_put.uid = uid;
     op.u.flow_put.stats = stats;
 
     opp = &op;
@@ -899,8 +906,8 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
 /* A dpif_operate() wrapper for performing a single DPIF_OP_FLOW_DEL. */
 int
 dpif_flow_del(struct dpif *dpif,
-              const struct nlattr *key, size_t key_len,
-              struct dpif_flow_stats *stats)
+              const struct nlattr *key, size_t key_len, const ovs_u128 *uid,
+              struct dpif_flow_stats *stats, bool terse_dump)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -908,6 +915,8 @@ dpif_flow_del(struct dpif *dpif,
     op.type = DPIF_OP_FLOW_DEL;
     op.u.flow_del.key = key;
     op.u.flow_del.key_len = key_len;
+    op.u.flow_del.uid = uid;
+    op.u.flow_del.terse = terse_dump;
     op.u.flow_del.stats = stats;
 
     opp = &op;
@@ -989,7 +998,7 @@ dpif_flow_dump_next(struct dpif_flow_dump_thread *thread,
         for (f = flows; f < &flows[n] && should_log_flow_message(0); f++) {
             log_flow_message(dpif, 0, "flow_dump",
                              f->key, f->key_len, f->mask, f->mask_len,
-                             &f->stats, f->actions, f->actions_len);
+                             &f->uid, &f->stats, f->actions, f->actions_len);
         }
     } else {
         VLOG_DBG_RL(&dpmsg_rl, "%s: dumped all flows", dpif_name(dpif));
@@ -1488,7 +1497,7 @@ static void
 log_flow_message(const struct dpif *dpif, int error, const char *operation,
                  const struct nlattr *key, size_t key_len,
                  const struct nlattr *mask, size_t mask_len,
-                 const struct dpif_flow_stats *stats,
+                 const ovs_u128 *uid, const struct dpif_flow_stats *stats,
                  const struct nlattr *actions, size_t actions_len)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
@@ -1500,6 +1509,10 @@ log_flow_message(const struct dpif *dpif, int error, const char *operation,
     if (error) {
         ds_put_format(&ds, "(%s) ", ovs_strerror(error));
     }
+    if (uid) {
+        odp_format_uid(uid, &ds);
+        ds_put_cstr(&ds, " ");
+    }
     odp_flow_format(key, key_len, mask, mask_len, NULL, &ds, true);
     if (stats) {
         ds_put_cstr(&ds, ", ");
@@ -1533,7 +1546,7 @@ log_flow_put_message(struct dpif *dpif, const struct dpif_flow_put *put,
         }
         log_flow_message(dpif, error, ds_cstr(&s),
                          put->key, put->key_len, put->mask, put->mask_len,
-                         put->stats, put->actions, put->actions_len);
+                         put->uid, put->stats, put->actions, put->actions_len);
         ds_destroy(&s);
     }
 }
@@ -1544,7 +1557,8 @@ log_flow_del_message(struct dpif *dpif, const struct dpif_flow_del *del,
 {
     if (should_log_flow_message(error)) {
         log_flow_message(dpif, error, "flow_del", del->key, del->key_len,
-                         NULL, 0, !error ? del->stats : NULL, NULL, 0);
+                         NULL, 0, del->uid, !error ? del->stats : NULL,
+                         NULL, 0);
     }
 }
 
@@ -1599,7 +1613,7 @@ log_flow_get_message(const struct dpif *dpif, const struct dpif_flow_get *get,
         log_flow_message(dpif, error, "flow_get",
                          get->key, get->key_len,
                          get->flow->mask, get->flow->mask_len,
-                         &get->flow->stats,
+                         get->uid, &get->flow->stats,
                          get->flow->actions, get->flow->actions_len);
     }
 }
diff --git a/lib/dpif.h b/lib/dpif.h
index 1689428..095fa01 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -521,13 +521,17 @@ int dpif_flow_put(struct dpif *, enum dpif_flow_put_flags,
                   const struct nlattr *key, size_t key_len,
                   const struct nlattr *mask, size_t mask_len,
                   const struct nlattr *actions, size_t actions_len,
-                  struct dpif_flow_stats *);
+                  const ovs_u128 *uid, struct dpif_flow_stats *);
+
 int dpif_flow_del(struct dpif *,
                   const struct nlattr *key, size_t key_len,
-                  struct dpif_flow_stats *);
+                  const ovs_u128 *uid, struct dpif_flow_stats *,
+                  bool terse);
 int dpif_flow_get(struct dpif *,
                   const struct nlattr *key, size_t key_len,
-                  struct ofpbuf *, struct dpif_flow *);
+                  const ovs_u128 *uid,
+                  struct ofpbuf *, struct dpif_flow *,
+                  bool terse);
 
 /* Flow dumping interface
  * ======================
@@ -624,6 +628,7 @@ struct dpif_flow_put {
     size_t mask_len;                /* Length of 'mask' in bytes. */
     const struct nlattr *actions;   /* Actions to perform on flow. */
     size_t actions_len;             /* Length of 'actions' in bytes. */
+    const ovs_u128 *uid;            /* Unique flow identifier. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -632,8 +637,14 @@ struct dpif_flow_put {
 /* Delete a flow.
  *
  * The flow is specified by the Netlink attributes with types OVS_KEY_ATTR_* in
- * the 'key_len' bytes starting at 'key'.  Succeeds with status 0 if the flow
- * is deleted, or fails with ENOENT if the dpif does not contain such a flow.
+ * the 'key_len' bytes starting at 'key', or the unique identifier 'uid'. At
+ * least one of 'key' or 'uid' must be specified. If both are specified, 'uid'
+ * takes precedence. Succeeds with status 0 if the flow is deleted, or fails
+ * with ENOENT if the dpif does not contain such a flow.
+ *
+ * If 'terse' is true, the dpif may only use 'uid' to delete the flow. Callers
+ * should still provide 'key' if it is available, to improve dpif logging in
+ * the event of errors or unexpected behaviour.
  *
  * If the operation succeeds, then 'stats', if nonnull, will be set to the
  * flow's statistics before its deletion. */
@@ -641,6 +652,9 @@ struct dpif_flow_del {
     /* Input. */
     const struct nlattr *key;       /* Flow to delete. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const ovs_u128 *uid;            /* UID of flow to delete. */
+    bool terse;                     /* Skip unnecessary fields when transacting
+                                       to datapath. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -674,8 +688,10 @@ struct dpif_execute {
 /* Queries the dpif for a flow entry.
  *
  * The flow is specified by the Netlink attributes with types OVS_KEY_ATTR_* in
- * the 'key_len' bytes starting at 'key'. 'buffer' must point to an initialized
- * buffer, with a recommended size of DPIF_FLOW_BUFSIZE bytes.
+ * the 'key_len' bytes starting at 'key', or the unique identifier 'uid'. At
+ * least one of 'key' or 'uid' must be specified. If both are specified, 'uid'
+ * takes precedence. 'buffer' must point to an initialized buffer, with a
+ * recommended size of DPIF_FLOW_BUFSIZE bytes.
  *
  * On success, 'flow' will be populated with the mask, actions and stats for
  * the datapath flow corresponding to 'key'. The mask and actions may point
@@ -683,6 +699,10 @@ struct dpif_execute {
  * that wish to hold these over quiescent periods must make a copy of these
  * fields before quiescing.
  *
+ * If 'terse' is true, the dpif may only use 'uid' to fetch the flow. Callers
+ * should still provide 'key' if it is available, to improve dpif logging in
+ * the event of errors or unexpected behaviour.
+ *
  * Succeeds with status 0 if the flow is fetched, or fails with ENOENT if no
  * such flow exists. Other failures are indicated with a positive errno value.
  */
@@ -690,7 +710,10 @@ struct dpif_flow_get {
     /* Input. */
     const struct nlattr *key;       /* Flow to get. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const ovs_u128 *uid;            /* UID of flow to get. */
     struct ofpbuf *buffer;          /* Storage for output parameters. */
+    bool terse;                     /* Skip unnecessary fields when transacting
+                                       to datapath. */
 
     /* Output. */
     struct dpif_flow *flow;         /* Resulting flow from datapath. */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 77e6ec5..109e08a 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -2851,6 +2851,60 @@ odp_flow_key_hash(const struct nlattr *key, size_t key_len)
                       key_len / sizeof(uint32_t), 0);
 }
 
+int
+odp_uid_from_nlattrs(const struct nlattr *nla, size_t nla_len, ovs_u128 *uid,
+                     uint32_t *flags)
+{
+    static const struct nl_policy ovs_uid_policy[] = {
+        [OVS_UID_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true },
+        [OVS_UID_ATTR_ID] = { .type = NL_A_UNSPEC,
+                              .min_len = sizeof *uid }
+    };
+    struct nlattr *a[ARRAY_SIZE(ovs_uid_policy)];
+    const ovs_u128 *uidp;
+    struct ofpbuf buf;
+
+    if (!nla) {
+        return EINVAL;
+    }
+
+    ofpbuf_use_const(&buf, nla, nla_len);
+    if (!nl_policy_parse(&buf, 0, ovs_uid_policy, a, ARRAY_SIZE(a))) {
+        VLOG_WARN("Failed to parse UID_ATTRs (%p, len=%"PRIuSIZE")",
+                  nla, nla_len);
+        return EINVAL;
+    }
+
+    uidp = nl_attr_get_unspec(a[OVS_UID_ATTR_ID], sizeof *uid);
+    memcpy(uid, uidp, sizeof *uid);
+
+    if (flags) {
+        if (a[OVS_UID_ATTR_FLAGS]) {
+            *flags = nl_attr_get_u32(a[OVS_UID_ATTR_FLAGS]);
+        } else {
+            *flags = 0;
+        }
+    }
+    return 0;
+}
+
+void
+odp_uid_to_nlattrs(struct ofpbuf *buf, const ovs_u128 *uid, uint32_t flags)
+{
+    if (flags) {
+        nl_msg_put_u32(buf, OVS_UID_ATTR_FLAGS, flags);
+    }
+    if (uid) {
+        nl_msg_put_unspec(buf, OVS_UID_ATTR_ID, uid, sizeof *uid);
+    }
+}
+
+void
+odp_format_uid(const ovs_u128 *uid, struct ds *ds)
+{
+    ds_put_format(ds, "uid:%016"PRIx64"%016"PRIx64, uid->u64.lo, uid->u64.hi);
+}
+
 static void
 log_odp_key_attributes(struct vlog_rate_limit *rl, const char *title,
                        uint64_t attrs, int out_of_range_attr,
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 11b54dd..23dcd07 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -142,6 +142,27 @@ struct odputil_keybuf {
     uint32_t keybuf[DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4)];
 };
 
+/* The maximum number of bytes that odp_uid_from_uid() appends to a buffer.
+ * This is the upper bound on the length of a nlattr-formatted flow key that
+ * ovs-vswitchd fully understands.
+ *
+ * As with ODPUTIL_FLOW_KEY_BYTES, the datapath may have a different idea of a
+ * flow, so this isn't necessarily an upper bound on the length of a UID that
+ * the datapath can pass to ovs-vswitchd.
+ *
+ *                                     struct  pad  nl hdr  total
+ *                                     ------  ---  ------  -----
+ * OVS_UID_ATTR_FLAGS                     4     -      4      8
+ * OVS_UID_ATTR_UID                      16     -      4     20
+ *  ------------------------------------------------------------
+ *  total                                                    28
+ *
+ */
+#define ODPUTIL_FLOW_UID_BYTES 28
+struct odputil_uidbuf {
+    uint32_t uidbuf[DIV_ROUND_UP(ODPUTIL_FLOW_UID_BYTES, 4)];
+};
+
 enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
                                            struct flow_tnl *);
 
@@ -162,6 +183,8 @@ void odp_flow_key_from_mask(struct ofpbuf *, const struct flow *mask,
                             size_t max_mpls_depth, bool recirc);
 
 uint32_t odp_flow_key_hash(const struct nlattr *, size_t);
+void odp_uid_to_nlattrs(struct ofpbuf *, const ovs_u128 *, uint32_t flags);
+void odp_format_uid(const ovs_u128 *uid, struct ds *);
 
 /* Estimated space needed for metadata. */
 enum { ODP_KEY_METADATA_SIZE = 9 * 8 };
@@ -188,6 +211,8 @@ enum odp_key_fitness odp_flow_key_to_mask(const struct nlattr *key, size_t len,
                                           struct flow *mask,
                                           const struct flow *flow);
 const char *odp_key_fitness_to_string(enum odp_key_fitness);
+int odp_uid_from_nlattrs(const struct nlattr *, size_t,
+                         ovs_u128 *uid, uint32_t *flags);
 
 void commit_odp_tunnel_action(const struct flow *, struct flow *base,
                               struct ofpbuf *odp_actions);
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 1ff4fab..916f26f 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -228,6 +228,7 @@ struct udpif_key {
 struct ukey_op {
     struct udpif_key *ukey;
     struct dpif_flow_stats stats; /* Stats for 'op'. */
+    struct odputil_uidbuf uid;    /* Storage for Netlink-formatted uid. */
     struct dpif_op dop;           /* Flow operation. */
 };
 
@@ -633,7 +634,8 @@ recv_upcalls(struct handler *handler)
                  * while traffic is being received.  Print a rate-limited
                  * message in case it happens frequently. */
                 dpif_flow_put(udpif->dpif, DPIF_FP_CREATE, dupcall->key,
-                              dupcall->key_len, NULL, 0, NULL, 0, NULL);
+                              dupcall->key_len, NULL, 0, NULL, 0,
+                              &dupcall->uid, NULL);
                 VLOG_INFO_RL(&rl, "received packet on unassociated datapath "
                              "port %"PRIu32, flow.in_port.odp_port);
             }
@@ -1153,6 +1155,7 @@ handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
             op->dop.u.flow_put.key_len = ukey->key_len;
             op->dop.u.flow_put.mask = ukey->mask;
             op->dop.u.flow_put.mask_len = ukey->mask_len;
+            op->dop.u.flow_put.uid = upcall->uid;
             op->dop.u.flow_put.stats = NULL;
             op->dop.u.flow_put.actions = ofpbuf_data(ukey->actions);
             op->dop.u.flow_put.actions_len = ofpbuf_size(ukey->actions);
@@ -1293,8 +1296,12 @@ ukey_install_start(struct udpif *udpif, struct udpif_key *new_ukey)
         } else {
             struct ds ds = DS_EMPTY_INITIALIZER;
 
+            odp_format_uid(&old_ukey->uid, &ds);
+            ds_put_cstr(&ds, " ");
             odp_flow_key_format(old_ukey->key, old_ukey->key_len, &ds);
             ds_put_cstr(&ds, "\n");
+            odp_format_uid(&new_ukey->uid, &ds);
+            ds_put_cstr(&ds, " ");
             odp_flow_key_format(new_ukey->key, new_ukey->key_len, &ds);
 
             VLOG_WARN("Conflicting ukey for flows:\n%s", ds_cstr(&ds));
@@ -1555,6 +1562,8 @@ delete_op_init(struct ukey_op *op, struct udpif_key *ukey)
     op->dop.type = DPIF_OP_FLOW_DEL;
     op->dop.u.flow_del.key = ukey->key;
     op->dop.u.flow_del.key_len = ukey->key_len;
+    op->dop.u.flow_del.uid = &ukey->uid;
+    op->dop.u.flow_del.terse = false;
     op->dop.u.flow_del.stats = &op->stats;
 }
 
@@ -1646,6 +1655,8 @@ log_unexpected_flow(const struct dpif_flow *flow)
     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
     struct ds ds = DS_EMPTY_INITIALIZER;
 
+    odp_format_uid(&flow->uid, &ds);
+    ds_put_cstr(&ds, " ");
     odp_flow_key_format(flow->key, flow->key_len, &ds);
     VLOG_INFO_RL(&rl, "Dumped flow from datapath with no corresponding ukey:\n"
                  "%s", ds_cstr(&ds));
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 1d964dd..b090721 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -982,6 +982,7 @@ check_recirc(struct dpif_backer *backer)
     struct flow flow;
     struct odputil_keybuf keybuf;
     struct ofpbuf key;
+    ovs_u128 uid;
     int error;
     bool enable_recirc = false;
 
@@ -992,9 +993,10 @@ check_recirc(struct dpif_backer *backer)
     ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
     odp_flow_key_from_flow(&key, &flow, NULL, 0, true);
 
+    dpif_flow_hash(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE,
                           ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0, NULL,
-                          0, NULL);
+                          0, &uid, NULL);
     if (error && error != EEXIST) {
         if (error != EINVAL) {
             VLOG_WARN("%s: Reciculation flow probe failed (%s)",
@@ -1004,7 +1006,7 @@ check_recirc(struct dpif_backer *backer)
     }
 
     error = dpif_flow_del(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
-                          NULL);
+                          &uid, NULL, false);
     if (error) {
         VLOG_WARN("%s: failed to delete recirculation feature probe flow",
                   dpif_name(backer->dpif));
@@ -1111,6 +1113,7 @@ check_max_mpls_depth(struct dpif_backer *backer)
     for (n = 0; n < FLOW_MAX_MPLS_LABELS; n++) {
         struct odputil_keybuf keybuf;
         struct ofpbuf key;
+        ovs_u128 uid;
         int error;
 
         memset(&flow, 0, sizeof flow);
@@ -1119,10 +1122,12 @@ check_max_mpls_depth(struct dpif_backer *backer)
 
         ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
         odp_flow_key_from_flow(&key, &flow, NULL, 0, false);
+        dpif_flow_hash(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
+                       &uid);
 
         error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE,
                               ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0,
-                              NULL, 0, NULL);
+                              NULL, 0, &uid, NULL);
         if (error && error != EEXIST) {
             if (error != EINVAL) {
                 VLOG_WARN("%s: MPLS stack length feature probe failed (%s)",
@@ -1132,7 +1137,7 @@ check_max_mpls_depth(struct dpif_backer *backer)
         }
 
         error = dpif_flow_del(backer->dpif, ofpbuf_data(&key),
-                              ofpbuf_size(&key), NULL);
+                              ofpbuf_size(&key), &uid, NULL, false);
         if (error) {
             VLOG_WARN("%s: failed to delete MPLS feature probe flow",
                       dpif_name(backer->dpif));
@@ -4937,6 +4942,8 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
             continue;
         }
 
+        odp_format_uid(&f.uid, &ds);
+        ds_put_cstr(&ds, " ");
         odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
                         &portno_names, &ds, verbosity);
         ds_put_cstr(&ds, ", ");
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index af7845c..5f17769 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -3,6 +3,7 @@ AT_BANNER([dpif-netdev])
 # Strips out uninteresting parts of flow output, as well as parts
 # that vary from one run to another (e.g., timing and bond actions).
 m4_define([STRIP_XOUT], [[sed '
+    s/uid:[0-9a-f]* //
     s/used:[0-9]*\.[0-9]*/used:0.0/
     s/actions:.*/actions: <del>/
     s/packets:[0-9]*/packets:0/
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 181e740..2f83d4b 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -4626,21 +4626,21 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:
 AT_CHECK([ovs-appctl netdev-dummy/receive p2 'in_port(2),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=0,code=0)'])
 AT_CHECK([ovs-appctl netdev-dummy/receive p3 'in_port(3),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
 ovs-appctl revalidator/wait
-AT_CHECK([ovs-appctl dpif/dump-flows br0 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows br0 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 recirc_id(0),in_port(1),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 recirc_id(0),in_port(2),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows br1 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows br1 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 recirc_id(0),in_port(3),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows -m br0 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows -m br0 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0/0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p1),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 skb_priority(0/0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p2),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=0/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows -m br1 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows -m br1 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0/0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p3),eth(src=50:54:00:00:00:09/00:00:00:00:00:00,dst=50:54:00:00:00:0a/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 ])
 
@@ -4675,7 +4675,7 @@ for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 for dl_src in 00 01; do
-    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
+    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:$dl_src,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208,mpls_lse2=0, actions:userspace(pid=0,slow_path(controller))
 ])
 done
@@ -4715,7 +4715,7 @@ for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 for dl_src in 00 01; do
-    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
+    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:$dl_src,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208,mpls_lse2=0, actions:userspace(pid=0,slow_path(controller))
 ])
 done
@@ -4769,15 +4769,15 @@ dummy at ovs-dummy: hit:13 missed:2
 		pbr1 1/none: (patch: peer=pbr0)
 ])
 
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,ip,in_port=100,nw_frag=no, actions:101,3,2
 recirc_id=0,ip,in_port=101,nw_frag=no, actions:100,2,3
 ])
 
-AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(100).*packets:9' | FILTER_FLOW_DUMP], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(100).*packets:9' | STRIP_UID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0/0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(100),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:9, bytes:540, used:0.0s, actions:101,3,2
 ])
-AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(101).*packets:4' | FILTER_FLOW_DUMP], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(101).*packets:4' | STRIP_UID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0/0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(101),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:4, bytes:240, used:0.0s, actions:100,2,3
 ])
 
@@ -5290,7 +5290,7 @@ sleep 1
 dnl The first packet is essentially a no-op, as the new destination MAC is the
 dnl same as the original.  The second entry actually updates the destination
 dnl MAC.
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,ip,in_port=1,dl_dst=50:54:00:00:00:0a,nw_frag=no, actions:2
 recirc_id=0,ip,in_port=1,dl_dst=50:54:00:00:00:0c,nw_frag=no, actions:set(eth(dst=50:54:00:00:00:0a)),2
 ])
@@ -5317,11 +5317,11 @@ for i in 1 2 3 4; do
     fi
 done
 sleep 1
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0, actions:2
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0, actions:drop
 ])
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_DUMP | grep 'packets:3'], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_DUMP | grep 'packets:3'], [0], [dnl
 skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0), packets:3, bytes:180, used:0.0s, actions:2
 skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0), packets:3, bytes:180, used:0.0s, actions:drop
 ])
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 77b9b39..80ec8d3 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -36,6 +36,7 @@ m4_divert_pop([PREPARE_TESTS])
 m4_define([STRIP_XIDS], [[sed 's/ (xid=0x[0-9a-fA-F]*)//']])
 m4_define([STRIP_DURATION], [[sed 's/\bduration=[0-9.]*s/duration=?s/']])
 m4_define([STRIP_USED], [[sed 's/used:[0-9]\.[0-9]*/used:0.0/']])
+m4_define([STRIP_UID], [[sed 's/uid:[0-9a-f]* //']])
 m4_define([TESTABLE_LOG], [-vPATTERN:ANY:'%c|%p|%m'])
 
 # OVS_VSWITCHD_START([vsctl-args], [vsctl-output], [=override])
-- 
1.7.10.4




More information about the dev mailing list