[ovs-dev] [PATCH 16/16] Implement learned flow deletion.

Ben Pfaff blp at nicira.com
Fri Jun 6 05:02:10 UTC 2014


When a flow with a "learn" action is deleted, one often wants the flows
that it created (the "learned flows") to be deleted as well.  This commit
makes that possible.

I am aware of a race condition that could lead to a learned flow not being
properly deleted.  Suppose thread A deletes a flow with a "learn" action.
Meanwhile, thread B obtains the actions for this flow and translates and
executes them.  Thread B could obtain the actions for the flow before it is
deleted, but execute them after the "learn" flow and its learned flows are
deleted.  The result is that the flow created by thread B persists despite
its "learn" flow having been deleted.  This race can and should be fixed,
but I think that this commit is worth reviewing without it.

VMware-BZ: #1254021
Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 NEWS                          |   3 +
 include/openflow/nicira-ext.h |  36 ++++++++-
 lib/learn.c                   |  26 ++++---
 lib/ofp-actions.h             |   2 +-
 ofproto/ofproto-provider.h    |   7 +-
 ofproto/ofproto.c             | 171 ++++++++++++++++++++++++++++++++++++++++++
 tests/learn.at                | 138 +++++++++++++++++++++++++++++++++-
 utilities/ovs-ofctl.8.in      |  10 +++
 8 files changed, 375 insertions(+), 18 deletions(-)

diff --git a/NEWS b/NEWS
index 82a61e1..23d0523 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,8 @@
 Post-v2.3.0
 ---------------------
+   - The "learn" action supports a new flag "delete_learned" that causes
+     the learned flows to be deleted when the flow with the "learn" action
+     is deleted.
 
 
 v2.3.0 - xx xxx xxxx
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 767b98b..cb3413a 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -927,7 +927,7 @@ struct nx_action_learn {
     ovs_be16 hard_timeout;      /* Max time before discarding (seconds). */
     ovs_be16 priority;          /* Priority level of flow entry. */
     ovs_be64 cookie;            /* Cookie for new flow. */
-    ovs_be16 flags;             /* Either 0 or OFPFF_SEND_FLOW_REM. */
+    ovs_be16 flags;             /* NX_LEARN_F_*. */
     uint8_t table_id;           /* Table to insert flow entry. */
     uint8_t pad;                /* Must be zero. */
     ovs_be16 fin_idle_timeout;  /* Idle timeout after FIN, if nonzero. */
@@ -937,6 +937,38 @@ struct nx_action_learn {
 };
 OFP_ASSERT(sizeof(struct nx_action_learn) == 32);
 
+/* Bits for 'flags' in struct nx_action_learn.
+ *
+ * If NX_LEARN_F_SEND_FLOW_REM is set, then the learned flows will have their
+ * OFPFF_SEND_FLOW_REM flag set.
+ *
+ * If NX_LEARN_F_DELETE_LEARNED is set, then removing this action will delete
+ * all the flows from the learn action's 'table_id' that have the learn
+ * action's 'cookie'.  Important points:
+ *
+ *     - The deleted flows include those created by this action, those created
+ *       by other learn actions with the same 'table_id' and 'cookie', those
+ *       created by flow_mod requests by a controller in the specified table
+ *       with the specified cookie, and those created through any other
+ *       means.
+ *
+ *     - If multiple flows specify "learn" actions with
+ *       NX_LEARN_F_DELETE_LEARNED with the same 'table_id' and 'cookie', then
+ *       no deletion occurs until all of those "learn" actions are deleted.
+ *
+ *     - Deleting a flow that contains a learn action is the most obvious way
+ *       to delete a learn action.  Modifying a flow's actions, or replacing it
+ *       by a new flow, can also delete a learn action.  Finally, replacing a
+ *       learn action with NX_LEARN_F_DELETE_LEARNED with a learn action
+ *       without that flag also effectively deletes the learn action and can
+ *       trigger flow deletion.
+ *
+ * NX_LEARN_F_DELETE_LEARNED was added in Open vSwitch 2.4. */
+enum nx_learn_flags {
+    NX_LEARN_F_SEND_FLOW_REM = 1 << 0,
+    NX_LEARN_F_DELETE_LEARNED = 1 << 1,
+};
+
 #define NX_LEARN_N_BITS_MASK    0x3ff
 
 #define NX_LEARN_SRC_FIELD     (0 << 13) /* Copy from field. */
diff --git a/lib/learn.c b/lib/learn.c
index 90991b1..a811779 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -101,15 +101,9 @@ learn_from_openflow(const struct nx_action_learn *nal, struct ofpbuf *ofpacts)
     learn->fin_idle_timeout = ntohs(nal->fin_idle_timeout);
     learn->fin_hard_timeout = ntohs(nal->fin_hard_timeout);
 
-    /* We only support "send-flow-removed" for now. */
-    switch (ntohs(nal->flags)) {
-    case 0:
-        learn->flags = 0;
-        break;
-    case OFPFF_SEND_FLOW_REM:
-        learn->flags = OFPUTIL_FF_SEND_FLOW_REM;
-        break;
-    default:
+    learn->flags = ntohs(nal->flags);
+    if (learn->flags & ~(NX_LEARN_F_SEND_FLOW_REM |
+                         NX_LEARN_F_DELETE_LEARNED)) {
         return OFPERR_OFPBAC_BAD_ARGUMENT;
     }
 
@@ -321,7 +315,10 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
     fm->hard_timeout = learn->hard_timeout;
     fm->buffer_id = UINT32_MAX;
     fm->out_port = OFPP_NONE;
-    fm->flags = learn->flags;
+    fm->flags = 0;
+    if (learn->flags & NX_LEARN_F_SEND_FLOW_REM) {
+        fm->flags |= OFPUTIL_FF_SEND_FLOW_REM;
+    }
     fm->ofpacts = NULL;
     fm->ofpacts_len = 0;
 
@@ -581,7 +578,9 @@ learn_parse__(char *orig, char *arg, struct ofpbuf *ofpacts)
         } else if (!strcmp(name, "cookie")) {
             learn->cookie = htonll(strtoull(value, NULL, 0));
         } else if (!strcmp(name, "send_flow_rem")) {
-            learn->flags |= OFPFF_SEND_FLOW_REM;
+            learn->flags |= NX_LEARN_F_SEND_FLOW_REM;
+        } else if (!strcmp(name, "delete_learned")) {
+            learn->flags |= NX_LEARN_F_DELETE_LEARNED;
         } else {
             struct ofpact_learn_spec *spec;
             char *error;
@@ -655,9 +654,12 @@ learn_format(const struct ofpact_learn *learn, struct ds *s)
     if (learn->priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, ",priority=%"PRIu16, learn->priority);
     }
-    if (learn->flags & OFPFF_SEND_FLOW_REM) {
+    if (learn->flags & NX_LEARN_F_SEND_FLOW_REM) {
         ds_put_cstr(s, ",send_flow_rem");
     }
+    if (learn->flags & NX_LEARN_F_DELETE_LEARNED) {
+        ds_put_cstr(s, ",delete_learned");
+    }
     if (learn->cookie != 0) {
         ds_put_format(s, ",cookie=%#"PRIx64, ntohll(learn->cookie));
     }
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 86522ab..8cb8862 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -482,7 +482,7 @@ struct ofpact_learn {
     uint16_t priority;          /* Priority level of flow entry. */
     uint8_t table_id;           /* Table to insert flow entry. */
     ovs_be64 cookie;            /* Cookie for new flow. */
-    enum ofputil_flow_mod_flags flags;
+    enum nx_learn_flags flags;  /* NX_LEARN_F_*. */
     uint16_t fin_idle_timeout;  /* Idle timeout after FIN, if nonzero. */
     uint16_t fin_hard_timeout;  /* Hard timeout after FIN, if nonzero. */
 
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 9c6b960..c8d70e6 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -96,6 +96,7 @@ struct ofproto {
 
     /* Rules indexed on their cookie values, in all flow tables. */
     struct hindex cookies OVS_GUARDED_BY(ofproto_mutex);
+    struct hmap learned_cookies OVS_GUARDED_BY(ofproto_mutex);
 
     /* List of expirable flows, in all flow tables. */
     struct list expirable OVS_GUARDED_BY(ofproto_mutex);
@@ -405,8 +406,12 @@ static inline bool rule_is_hidden(const struct rule *);
 struct rule_actions {
     /* Flags.
      *
-     * 'has_meter' is true if 'ofpacts' contains an OFPACT_METER action. */
+     * 'has_meter' is true if 'ofpacts' contains an OFPACT_METER action.
+     *
+     * 'has_learn_with_delete' is true if 'ofpacts' contains an OFPACT_LEARN
+     * action whose flags include NX_LEARN_F_DELETE_LEARNED. */
     bool has_meter;
+    bool has_learn_with_delete;
 
     /* Actions. */
     uint32_t ofpacts_len;         /* Size of 'ofpacts', in bytes. */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 1ad3ba0..96bea34 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -157,6 +157,10 @@ static void rule_criteria_require_rw(struct rule_criteria *,
                                      bool override_readonly);
 static void rule_criteria_destroy(struct rule_criteria *);
 
+static enum ofperr collect_rules_loose(struct ofproto *,
+                                       const struct rule_criteria *,
+                                       struct rule_collection *);
+
 /* A packet that needs to be passed to rule_execute().
  *
  * (We can't do this immediately from ofopgroup_complete() because that holds
@@ -171,6 +175,37 @@ struct rule_execute {
 static void run_rule_executes(struct ofproto *) OVS_EXCLUDED(ofproto_mutex);
 static void destroy_rule_executes(struct ofproto *);
 
+struct learned_cookie {
+    union {
+        /* In struct ofproto's 'learned_cookies' hmap. */
+        struct hmap_node hmap_node OVS_GUARDED_BY(ofproto_mutex);
+
+        /* In 'dead_cookies' list when removed from hmap. */
+        struct list list_node;
+    } u;
+
+    /* Key. */
+    ovs_be64 cookie OVS_GUARDED_BY(ofproto_mutex);
+    uint8_t table_id OVS_GUARDED_BY(ofproto_mutex);
+
+    /* Number of references from "learn" actions.
+     *
+     * When this drops to 0, all of the flows in 'table_id' with the specified
+     * 'cookie' are deleted. */
+    int n OVS_GUARDED_BY(ofproto_mutex);
+};
+
+static const struct ofpact_learn *next_learn_with_delete(
+    const struct rule_actions *, const struct ofpact_learn *start);
+
+static void learned_cookies_inc(struct ofproto *, const struct rule_actions *)
+    OVS_REQUIRES(ofproto_mutex);
+static void learned_cookies_dec(struct ofproto *, const struct rule_actions *,
+                                struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex);
+static void learned_cookies_flush(struct ofproto *, struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex);
+
 /* ofport. */
 static void ofport_destroy__(struct ofport *) OVS_EXCLUDED(ofproto_mutex);
 static void ofport_destroy(struct ofport *);
@@ -473,6 +508,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
     ofproto->tables = NULL;
     ofproto->n_tables = 0;
     hindex_init(&ofproto->cookies);
+    hmap_init(&ofproto->learned_cookies);
     list_init(&ofproto->expirable);
     ofproto->connmgr = connmgr_create(ofproto, datapath_name, datapath_name);
     guarded_list_init(&ofproto->rule_executes);
@@ -1256,6 +1292,9 @@ ofproto_destroy__(struct ofproto *ofproto)
     ovs_assert(hindex_is_empty(&ofproto->cookies));
     hindex_destroy(&ofproto->cookies);
 
+    ovs_assert(hmap_is_empty(&ofproto->learned_cookies));
+    hmap_destroy(&ofproto->learned_cookies);
+
     free(ofproto->vlan_bitmap);
 
     ofproto->ofproto_class->dealloc(ofproto);
@@ -2498,6 +2537,9 @@ rule_actions_create(const struct ofpact *ofpacts, size_t ofpacts_len)
     actions->has_meter = ofpacts_get_meter(ofpacts, ofpacts_len) != 0;
     memcpy(actions->ofpacts, ofpacts, ofpacts_len);
 
+    actions->has_learn_with_delete = (next_learn_with_delete(actions, NULL)
+                                      != NULL);
+
     return actions;
 }
 
@@ -2590,6 +2632,122 @@ rule_is_readonly(const struct rule *rule)
     return (table->flags & OFTABLE_READONLY) != 0;
 }
 
+static uint32_t
+hash_learned_cookie(ovs_be64 cookie_, uint8_t table_id)
+{
+    uint64_t cookie = (OVS_FORCE uint64_t) cookie_;
+    return hash_3words(cookie, cookie >> 32, table_id);
+}
+
+static void
+learned_cookies_update_one__(struct ofproto *ofproto,
+                             const struct ofpact_learn *learn,
+                             int delta, struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    uint32_t hash = hash_learned_cookie(learn->cookie, learn->table_id);
+    struct learned_cookie *c;
+
+    HMAP_FOR_EACH_WITH_HASH (c, u.hmap_node, hash, &ofproto->learned_cookies) {
+        if (c->cookie == learn->cookie && c->table_id == learn->table_id) {
+            c->n += delta;
+            ovs_assert(c->n >= 0);
+
+            if (!c->n) {
+                hmap_remove(&ofproto->learned_cookies, &c->u.hmap_node);
+                list_push_back(dead_cookies, &c->u.list_node);
+            }
+
+            return;
+        }
+    }
+
+    ovs_assert(delta > 0);
+    c = xmalloc(sizeof *c);
+    hmap_insert(&ofproto->learned_cookies, &c->u.hmap_node, hash);
+    c->cookie = learn->cookie;
+    c->table_id = learn->table_id;
+    c->n = delta;
+}
+
+static const struct ofpact_learn *
+next_learn_with_delete(const struct rule_actions *actions,
+                       const struct ofpact_learn *start)
+{
+    const struct ofpact *pos;
+
+    for (pos = start ? ofpact_next(&start->ofpact) : actions->ofpacts;
+         pos < ofpact_end(actions->ofpacts, actions->ofpacts_len);
+         pos = ofpact_next(pos)) {
+        if (pos->type == OFPACT_LEARN) {
+            const struct ofpact_learn *learn = ofpact_get_LEARN(pos);
+            if (learn->flags & NX_LEARN_F_DELETE_LEARNED) {
+                return learn;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static void
+learned_cookies_update__(struct ofproto *ofproto,
+                         const struct rule_actions *actions,
+                         int delta, struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    if (actions->has_learn_with_delete) {
+        const struct ofpact_learn *learn;
+
+        for (learn = next_learn_with_delete(actions, NULL); learn;
+             learn = next_learn_with_delete(actions, learn)) {
+            learned_cookies_update_one__(ofproto, learn, delta, dead_cookies);
+        }
+    }
+}
+
+static void
+learned_cookies_inc(struct ofproto *ofproto,
+                    const struct rule_actions *actions)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    learned_cookies_update__(ofproto, actions, +1, NULL);
+}
+
+static void
+learned_cookies_dec(struct ofproto *ofproto,
+                    const struct rule_actions *actions,
+                    struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    learned_cookies_update__(ofproto, actions, -1, dead_cookies);
+}
+
+static void
+learned_cookies_flush(struct ofproto *ofproto, struct list *dead_cookies)
+    OVS_REQUIRES(ofproto_mutex)
+{
+    struct learned_cookie *c, *next;
+
+    LIST_FOR_EACH_SAFE (c, next, u.list_node, dead_cookies) {
+        struct rule_criteria criteria;
+        struct rule_collection rules;
+        struct match match;
+
+        match_init_catchall(&match);
+        rule_criteria_init(&criteria, c->table_id, &match, 0,
+                           c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
+        rule_criteria_require_rw(&criteria, false);
+        collect_rules_loose(ofproto, &criteria, &rules);
+        delete_flows__(&rules, OFPRR_DELETE, NULL);
+        rule_criteria_destroy(&criteria);
+        rule_collection_destroy(&rules);
+
+        list_remove(&c->u.list_node);
+        free(c);
+    }
+}
+
 static enum ofperr
 handle_echo_request(struct ofconn *ofconn, const struct ofp_header *oh)
 {
@@ -3896,6 +4054,7 @@ add_flow(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
         ofproto_rule_unref(rule);
         return error;
     }
+    learned_cookies_inc(ofproto, actions);
 
     if (minimask_get_vid_mask(&rule->cr.match.mask) == VLAN_VID_MASK) {
         if (ofproto->vlan_bitmap) {
@@ -3930,6 +4089,7 @@ modify_flows__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
                const struct flow_mod_requester *req)
     OVS_REQUIRES(ofproto_mutex)
 {
+    struct list dead_cookies = LIST_INITIALIZER(&dead_cookies);
     enum nx_flow_update_event event;
     enum ofperr error;
     size_t i;
@@ -4010,7 +4170,13 @@ modify_flows__(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
             ofmonitor_report(ofproto->connmgr, rule, event, 0,
                              req ? req->ofconn : NULL, req ? req->xid : 0);
         }
+
+        if (change_actions) {
+            learned_cookies_inc(ofproto, rule_get_actions(rule));
+            learned_cookies_dec(ofproto, actions, &dead_cookies);
+        }
     }
+    learned_cookies_flush(ofproto, &dead_cookies);
 
     if (fm->buffer_id != UINT32_MAX && req) {
         error = send_buffered_packet(req->ofconn, fm->buffer_id,
@@ -4104,11 +4270,13 @@ delete_flows__(const struct rule_collection *rules,
     OVS_REQUIRES(ofproto_mutex)
 {
     if (rules->n) {
+        struct list dead_cookies = LIST_INITIALIZER(&dead_cookies);
         struct ofproto *ofproto = rules->rules[0]->ofproto;
         size_t i;
 
         for (i = 0; i < rules->n; i++) {
             struct rule *rule = rules->rules[i];
+            const struct rule_actions *actions = rule_get_actions(rule);
 
             ofproto_rule_send_removed(rule, reason);
 
@@ -4116,7 +4284,10 @@ delete_flows__(const struct rule_collection *rules,
                              req ? req->ofconn : NULL, req ? req->xid : 0);
             oftable_remove_rule(rule);
             ofproto->ofproto_class->rule_delete(rule);
+
+            learned_cookies_dec(ofproto, actions, &dead_cookies);
         }
+        learned_cookies_flush(ofproto, &dead_cookies);
         ofmonitor_flush(ofproto->connmgr);
     }
 }
diff --git a/tests/learn.at b/tests/learn.at
index 3c304d1..2ca58fc 100644
--- a/tests/learn.at
+++ b/tests/learn.at
@@ -3,6 +3,9 @@ AT_BANNER([learning action])
 AT_SETUP([learning action - parsing and formatting])
 AT_DATA([flows.txt], [[
 actions=learn()
+actions=learn(send_flow_rem)
+actions=learn(delete_learned)
+actions=learn(send_flow_rem,delete_learned)
 actions=learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[], load:10->NXM_NX_REG0[5..10])
 actions=learn(table=1,idle_timeout=10, hard_timeout=20, fin_idle_timeout=5, fin_hard_timeout=10, priority=10, cookie=0xfedcba9876543210, in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
 ]])
@@ -10,8 +13,11 @@ AT_CHECK([ovs-ofctl parse-flows flows.txt], [0],
 [[usable protocols: any
 chosen protocol: OpenFlow10-table_id
 OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1)
-OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0xa->NXM_NX_REG0[5..10])
-OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=10,hard_timeout=20,fin_idle_timeout=5,fin_hard_timeout=10,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
+OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,send_flow_rem)
+OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,delete_learned)
+OFPT_FLOW_MOD (xid=0x4): ADD actions=learn(table=1,send_flow_rem,delete_learned)
+OFPT_FLOW_MOD (xid=0x5): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0xa->NXM_NX_REG0[5..10])
+OFPT_FLOW_MOD (xid=0x6): ADD actions=learn(table=1,idle_timeout=10,hard_timeout=20,fin_idle_timeout=5,fin_hard_timeout=10,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31])
 ]])
 AT_CLEANUP
 
@@ -480,3 +486,131 @@ AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip], [0],
 ])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([learning action - delete_learned feature])
+OVS_VSWITCHD_START
+
+# Add some initial flows and check that it was successful.
+AT_DATA([flows.txt], [dnl
+                       reg0=0x1 actions=learn(delete_learned,cookie=0x123)
+                       reg0=0x2 actions=learn(delete_learned,cookie=0x123)
+cookie=0x123, table=1, reg0=0x3 actions=drop
+cookie=0x123, table=1, reg0=0x4 actions=drop
+cookie=0x123, table=2, reg0=0x5 actions=drop
+cookie=0x234, table=1, reg0=0x6 actions=drop
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=1, reg0=0x3 actions=drop
+ cookie=0x123, table=1, reg0=0x4 actions=drop
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+ cookie=0x234, table=1, reg0=0x6 actions=drop
+ reg0=0x1 actions=learn(table=1,delete_learned,cookie=0x123)
+ reg0=0x2 actions=learn(table=1,delete_learned,cookie=0x123)
+NXST_FLOW reply:
+])
+
+# Delete one of the learn actions.  The learned flows should stay, since there
+# is another learn action with the identical target.
+AT_CHECK([ovs-ofctl del-flows br0 'table=0 reg0=1'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=1, reg0=0x3 actions=drop
+ cookie=0x123, table=1, reg0=0x4 actions=drop
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+ cookie=0x234, table=1, reg0=0x6 actions=drop
+ reg0=0x2 actions=learn(table=1,delete_learned,cookie=0x123)
+NXST_FLOW reply:
+])
+
+# Change the flow with the learn action by adding a second action.  The learned
+# flows should stay because the learn action is still there.
+AT_CHECK([ovs-ofctl mod-flows br0 'table=0 reg0=2 actions=output:1,learn(delete_learned,cookie=0x123)'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=1, reg0=0x3 actions=drop
+ cookie=0x123, table=1, reg0=0x4 actions=drop
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+ cookie=0x234, table=1, reg0=0x6 actions=drop
+ reg0=0x2 actions=output:1,learn(table=1,delete_learned,cookie=0x123)
+NXST_FLOW reply:
+])
+
+# Change the flow with the learn action by replacing its learn action by one
+# with a different target.  The (previous) learned flows disappear.
+AT_CHECK([ovs-ofctl mod-flows br0 'table=0 reg0=2 actions=learn(delete_learned,cookie=0x234)'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+ cookie=0x234, table=1, reg0=0x6 actions=drop
+ reg0=0x2 actions=learn(table=1,delete_learned,cookie=0x234)
+NXST_FLOW reply:
+])
+
+# Use add-flow to replace the flow with the learn action by one with the
+# same learn action and an extra action.  The (new) learned flow remains.
+AT_CHECK([ovs-ofctl add-flow br0 'table=0 reg0=2 actions=learn(delete_learned,cookie=0x234),output:2'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+ cookie=0x234, table=1, reg0=0x6 actions=drop
+ reg0=0x2 actions=learn(table=1,delete_learned,cookie=0x234),output:2
+NXST_FLOW reply:
+])
+
+# Delete the flow with the learn action.  The learned flow disappears too.
+AT_CHECK([ovs-ofctl del-flows br0 table=0])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=2, reg0=0x5 actions=drop
+NXST_FLOW reply:
+])
+
+# Add a new set of flows to check on a corner case: the learned flows
+# contain their own learn actions which cascade to further deletions.
+# This can't happen if the learned flows were actually created by a
+# learn action, since the learn action has very restricted action
+# support, but there's no restriction that the deleted flows were
+# created by a learn action.
+AT_DATA([flows.txt], [dnl
+                       reg0=0x1 actions=learn(table=1,delete_learned,cookie=0x123)
+                       reg0=0x2 actions=learn(table=2,delete_learned,cookie=0x234)
+cookie=0x123, table=1, reg0=0x3 actions=learn(table=3,delete_learned,cookie=0x345)
+cookie=0x234, table=2, reg0=0x3 actions=learn(table=4,delete_learned,cookie=0x456)
+cookie=0x345, table=3, reg0=0x4 actions=learn(table=5,delete_learned,cookie=0x567)
+cookie=0x456, table=4, reg0=0x5 actions=learn(table=5,delete_learned,cookie=0x567)
+cookie=0x567, table=5, reg0=0x6 actions=drop
+cookie=0x567, table=5, reg0=0x7 actions=drop
+cookie=0x567, table=5, reg0=0x8 actions=drop
+])
+AT_CHECK([ovs-ofctl del-flows br0])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x123, table=1, reg0=0x3 actions=learn(table=3,delete_learned,cookie=0x345)
+ cookie=0x234, table=2, reg0=0x3 actions=learn(table=4,delete_learned,cookie=0x456)
+ cookie=0x345, table=3, reg0=0x4 actions=learn(table=5,delete_learned,cookie=0x567)
+ cookie=0x456, table=4, reg0=0x5 actions=learn(table=5,delete_learned,cookie=0x567)
+ cookie=0x567, table=5, reg0=0x6 actions=drop
+ cookie=0x567, table=5, reg0=0x7 actions=drop
+ cookie=0x567, table=5, reg0=0x8 actions=drop
+ reg0=0x1 actions=learn(table=1,delete_learned,cookie=0x123)
+ reg0=0x2 actions=learn(table=2,delete_learned,cookie=0x234)
+NXST_FLOW reply:
+])
+
+# Deleting the flow with reg0=1 should cascade to delete a few levels
+# of learned flows, but the ones with cookie=0x567 stick around
+# because of the flow with cookie=0x456.
+AT_CHECK([ovs-ofctl del-flows br0 'table=0 reg0=1'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+ cookie=0x234, table=2, reg0=0x3 actions=learn(table=4,delete_learned,cookie=0x456)
+ cookie=0x456, table=4, reg0=0x5 actions=learn(table=5,delete_learned,cookie=0x567)
+ cookie=0x567, table=5, reg0=0x6 actions=drop
+ cookie=0x567, table=5, reg0=0x7 actions=drop
+ cookie=0x567, table=5, reg0=0x8 actions=drop
+ reg0=0x2 actions=learn(table=2,delete_learned,cookie=0x234)
+NXST_FLOW reply:
+])
+
+# Deleting the flow with reg0=2 should cascade to delete all the rest:
+AT_CHECK([ovs-ofctl del-flows br0 'table=0 reg0=2'])
+AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
+NXST_FLOW reply:
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index ad9fe78..cc811ff 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1491,6 +1491,16 @@ The table in which the new flow should be inserted.  Specify a decimal
 number between 0 and 254.  The default, if \fBtable\fR is unspecified,
 is table 1.
 .
+.IP \fBdelete_learned\fR
+This flag enables deletion of the learned flows when the flow with the
+\fBlearn\fR action is removed.  Specifically, when the last
+\fBlearn\fR action with this flag and particular \fBtable\fR and
+\fBcookie\fR values is removed, the switch deletes all of the flows in
+the specified table with the specified cookie.
+.
+.IP
+This flag was added in Open vSwitch 2.4.
+.
 .IP \fIfield\fB=\fIvalue\fR
 .IQ \fIfield\fB[\fIstart\fB..\fIend\fB]=\fIsrc\fB[\fIstart\fB..\fIend\fB]\fR
 .IQ \fIfield\fB[\fIstart\fB..\fIend\fB]\fR
-- 
1.9.1




More information about the dev mailing list