2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

Implement learned flow deletion.

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@nicira.com>
Acked-by: Thomas Graf <tgraf@suug.ch>
Acked-by: Ethan Jackson <ethan@nicira.com>
This commit is contained in:
Ben Pfaff 2014-06-05 21:53:34 -07:00
parent 9ca4a86fff
commit 35f48b8bd9
8 changed files with 375 additions and 18 deletions

3
NEWS
View File

@ -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

View File

@ -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. */

View File

@ -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;
fm->delete_reason = OFPRR_DELETE;
@ -582,7 +579,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;
@ -656,9 +655,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));
}

View File

@ -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. */

View File

@ -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. */

View File

@ -161,6 +161,10 @@ static void rule_criteria_require_rw(struct rule_criteria *,
bool can_write_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
@ -175,6 +179,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 *);
@ -477,6 +512,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);
@ -1263,6 +1299,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);
@ -2506,6 +2545,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;
}
@ -2598,6 +2640,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)
{
@ -3904,6 +4062,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) {
@ -3938,6 +4097,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;
@ -4018,7 +4178,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,
@ -4112,11 +4278,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);
@ -4124,7 +4292,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);
}
}

View File

@ -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

View File

@ -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