diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | include/openflow/nicira-ext.h | 36 | ||||
-rw-r--r-- | lib/learn.c | 26 | ||||
-rw-r--r-- | lib/ofp-actions.h | 2 | ||||
-rw-r--r-- | ofproto/ofproto-provider.h | 7 | ||||
-rw-r--r-- | ofproto/ofproto.c | 171 | ||||
-rw-r--r-- | tests/learn.at | 138 | ||||
-rw-r--r-- | utilities/ovs-ofctl.8.in | 10 |
8 files changed, 375 insertions, 18 deletions
@@ -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 767b98b0a..cb3413a8d 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 911dd04f8..c5797ebba 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; 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)); } diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h index 86522ab4d..8cb88629c 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 9c6b960d9..c8d70e615 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 660c308af..a3ab2763d 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -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); } } diff --git a/tests/learn.at b/tests/learn.at index 3c304d10c..2ca58fc22 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 ad9fe783e..cc811ffc8 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 |