]> git.proxmox.com Git - ovs.git/blobdiff - ofproto/ofproto.c
ofproto: Make ofproto_rule_is_hidden() public, and rename.
[ovs.git] / ofproto / ofproto.c
index 7952984ab9f5961239ecd91fb80e3d089cd16cd1..fb768db5d4ba83040a2a2a9b755f50632299ea1c 100644 (file)
@@ -163,7 +163,6 @@ static void oftable_enable_eviction(struct oftable *,
 static void oftable_remove_rule(struct rule *rule) OVS_REQUIRES(ofproto_mutex);
 static void oftable_remove_rule__(struct ofproto *, struct rule *)
     OVS_REQUIRES(ofproto_mutex);
-static void oftable_insert_rule(struct rule *);
 
 /* A set of rules within a single OpenFlow table (oftable) that have the same
  * values for the oftable's eviction_fields.  A rule to be evicted, when one is
@@ -187,11 +186,16 @@ struct eviction_group {
     struct heap rules;          /* Contains "struct rule"s. */
 };
 
-static bool choose_rule_to_evict(struct oftable *table, struct rule **rulep);
-static void ofproto_evict(struct ofproto *) OVS_EXCLUDED(ofproto_mutex);
-static uint32_t rule_eviction_priority(struct ofproto *ofproto, struct rule *);
-static void eviction_group_add_rule(struct rule *);
-static void eviction_group_remove_rule(struct rule *);
+static bool choose_rule_to_evict(struct oftable *table, struct rule **rulep)
+    OVS_REQUIRES(ofproto_mutex);
+static void ofproto_evict(struct ofproto *)
+    OVS_EXCLUDED(ofproto_mutex);
+static uint32_t rule_eviction_priority(struct ofproto *ofproto, struct rule *)
+    OVS_REQUIRES(ofproto_mutex);;
+static void eviction_group_add_rule(struct rule *)
+    OVS_REQUIRES(ofproto_mutex);
+static void eviction_group_remove_rule(struct rule *)
+    OVS_REQUIRES(ofproto_mutex);
 
 /* Criteria that flow_mod and other operations use for selecting rules on
  * which to operate. */
@@ -273,9 +277,6 @@ static bool rule_is_modifiable(const struct rule *rule,
 static enum ofperr add_flow(struct ofproto *, struct ofconn *,
                             struct ofputil_flow_mod *,
                             const struct ofp_header *);
-static void do_add_flow(struct ofproto *, struct ofconn *,
-                        const struct ofp_header *request, uint32_t buffer_id,
-                        struct rule *);
 static enum ofperr modify_flows__(struct ofproto *, struct ofconn *,
                                   struct ofputil_flow_mod *,
                                   const struct ofp_header *,
@@ -304,6 +305,7 @@ static uint64_t pick_fallback_dpid(void);
 static void ofproto_destroy__(struct ofproto *);
 static void update_mtu(struct ofproto *, struct ofport *);
 static void meter_delete(struct ofproto *, uint32_t first, uint32_t last);
+static void meter_insert_rule(struct rule *);
 
 /* unixctl. */
 static void ofproto_unixctl_init(void);
@@ -1341,6 +1343,9 @@ ofproto_destroy__(struct ofproto *ofproto)
 
     hmap_destroy(&ofproto->deletions);
 
+    ovs_assert(hindex_is_empty(&ofproto->cookies));
+    hindex_destroy(&ofproto->cookies);
+
     free(ofproto->vlan_bitmap);
 
     ofproto->ofproto_class->dealloc(ofproto);
@@ -1865,6 +1870,7 @@ flow_mod_init(struct ofputil_flow_mod *fm,
     fm->flags = 0;
     fm->ofpacts = CONST_CAST(struct ofpact *, ofpacts);
     fm->ofpacts_len = ofpacts_len;
+    fm->delete_reason = OFPRR_DELETE;
 }
 
 static int
@@ -1978,47 +1984,6 @@ ofproto_flow_mod(struct ofproto *ofproto, struct ofputil_flow_mod *fm)
     return handle_flow_mod__(ofproto, NULL, fm, NULL);
 }
 
-/* Resets the modified time for 'rule' or an equivalent rule. If 'rule' is not
- * in the classifier, but an equivalent rule is, unref 'rule' and ref the new
- * rule. Otherwise if 'rule' is no longer installed in the classifier,
- * reinstall it.
- *
- * Returns the rule whose modified time has been reset. */
-struct rule *
-ofproto_refresh_rule(struct rule *rule)
-{
-    const struct oftable *table = &rule->ofproto->tables[rule->table_id];
-    const struct cls_rule *cr = &rule->cr;
-    struct rule *r;
-
-    /* do_add_flow() requires that the rule is not installed. We lock the
-     * ofproto_mutex here so that another thread cannot add the flow before
-     * we get a chance to add it.*/
-    ovs_mutex_lock(&ofproto_mutex);
-
-    fat_rwlock_rdlock(&table->cls.rwlock);
-    r = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls, cr));
-    if (r != rule) {
-        ofproto_rule_ref(r);
-    }
-    fat_rwlock_unlock(&table->cls.rwlock);
-
-    if (!r) {
-        do_add_flow(rule->ofproto, NULL, NULL, 0, rule);
-    } else if  (r != rule) {
-        ofproto_rule_unref(rule);
-        rule = r;
-    }
-    ovs_mutex_unlock(&ofproto_mutex);
-
-    /* Refresh the modified time for the rule. */
-    ovs_mutex_lock(&rule->mutex);
-    rule->modified = MAX(rule->modified, time_msec());
-    ovs_mutex_unlock(&rule->mutex);
-
-    return rule;
-}
-
 /* Searches for a rule with matching criteria exactly equal to 'target' in
  * ofproto's table 0 and, if it finds one, deletes it.
  *
@@ -2792,17 +2757,6 @@ destroy_rule_executes(struct ofproto *ofproto)
     }
 }
 
-/* Returns true if 'rule' should be hidden from the controller.
- *
- * Rules with priority higher than UINT16_MAX are set up by ofproto itself
- * (e.g. by in-band control) and are intentionally hidden from the
- * controller. */
-static bool
-ofproto_rule_is_hidden(const struct rule *rule)
-{
-    return (rule->cr.priority > UINT16_MAX);
-}
-
 static bool
 oftable_is_modifiable(const struct oftable *table,
                       enum ofputil_flow_mod_flags flags)
@@ -3456,7 +3410,7 @@ collect_rule(struct rule *rule, const struct rule_criteria *c,
      * rules to be selected.  (This doesn't allow OpenFlow clients to meddle
      * with hidden flows because OpenFlow uses only a 16-bit field to specify
      * priority.) */
-    if (ofproto_rule_is_hidden(rule) && c->cr.priority <= UINT16_MAX) {
+    if (rule_is_hidden(rule) && c->cr.priority <= UINT16_MAX) {
         return 0;
     } else if (rule->pending) {
         return OFPROTO_POSTPONE;
@@ -4000,6 +3954,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
          struct ofputil_flow_mod *fm, const struct ofp_header *request)
     OVS_REQUIRES(ofproto_mutex)
 {
+    const struct rule_actions *actions;
+    struct ofopgroup *group;
     struct oftable *table;
     struct cls_rule cr;
     struct rule *rule;
@@ -4121,8 +4077,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
 
     *CONST_CAST(uint8_t *, &rule->table_id) = table - ofproto->tables;
     rule->flags = fm->flags & OFPUTIL_FF_STATE;
-    ovsrcu_set(&rule->actions,
-               rule_actions_create(ofproto, fm->ofpacts, fm->ofpacts_len));
+    actions = rule_actions_create(ofproto, fm->ofpacts, fm->ofpacts_len);
+    ovsrcu_set(&rule->actions, actions);
     list_init(&rule->meter_list_node);
     rule->eviction_group = NULL;
     list_init(&rule->expirable);
@@ -4137,26 +4093,25 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
         return error;
     }
 
-    /* Insert rule. */
-    do_add_flow(ofproto, ofconn, request, fm->buffer_id, rule);
-
-    return error;
-}
-
-static void
-do_add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
-            const struct ofp_header *request, uint32_t buffer_id,
-            struct rule *rule)
-    OVS_REQUIRES(ofproto_mutex)
-{
-    struct ofopgroup *group;
+    if (fm->hard_timeout || fm->idle_timeout) {
+        list_insert(&ofproto->expirable, &rule->expirable);
+    }
+    cookies_insert(ofproto, rule);
+    eviction_group_add_rule(rule);
+    if (actions->provider_meter_id != UINT32_MAX) {
+        meter_insert_rule(rule);
+    }
 
-    oftable_insert_rule(rule);
+    fat_rwlock_wrlock(&table->cls.rwlock);
+    classifier_insert(&table->cls, CONST_CAST(struct cls_rule *, &rule->cr));
+    fat_rwlock_unlock(&table->cls.rwlock);
 
-    group = ofopgroup_create(ofproto, ofconn, request, buffer_id);
+    group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
     ofoperation_create(group, rule, OFOPERATION_ADD, 0);
     ofproto->ofproto_class->rule_insert(rule);
     ofopgroup_submit(group);
+
+    return error;
 }
 \f
 /* OFPFC_MODIFY and OFPFC_MODIFY_STRICT. */
@@ -4179,6 +4134,20 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
     enum ofperr error;
     size_t i;
 
+    if (ofproto->ofproto_class->rule_premodify_actions) {
+        for (i = 0; i < rules->n; i++) {
+            struct rule *rule = rules->rules[i];
+
+            if (rule_is_modifiable(rule, fm->flags)) {
+                error = ofproto->ofproto_class->rule_premodify_actions(
+                    rule, fm->ofpacts, fm->ofpacts_len);
+                if (error) {
+                    return error;
+                }
+            }
+        }
+    }
+
     type = fm->command == OFPFC_ADD ? OFOPERATION_REPLACE : OFOPERATION_MODIFY;
     group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
     error = OFPERR_OFPBRC_EPERM;
@@ -4234,8 +4203,7 @@ modify_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
 
             ovsrcu_set(&rule->actions, new_actions);
 
-            rule->ofproto->ofproto_class->rule_modify_actions(rule,
-                                                              reset_counters);
+            ofproto->ofproto_class->rule_modify_actions(rule, reset_counters);
         } else {
             ofoperation_complete(op, 0);
         }
@@ -4376,7 +4344,8 @@ delete_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
     rule_criteria_destroy(&criteria);
 
     if (!error && rules.n > 0) {
-        error = delete_flows__(ofproto, ofconn, request, &rules, OFPRR_DELETE);
+        error = delete_flows__(ofproto, ofconn, request, &rules,
+                               fm->delete_reason);
     }
     rule_collection_destroy(&rules);
 
@@ -4401,7 +4370,8 @@ delete_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
     rule_criteria_destroy(&criteria);
 
     if (!error && rules.n > 0) {
-        error = delete_flows__(ofproto, ofconn, request, &rules, OFPRR_DELETE);
+        error = delete_flows__(ofproto, ofconn, request, &rules,
+                               fm->delete_reason);
     }
     rule_collection_destroy(&rules);
 
@@ -4415,8 +4385,7 @@ ofproto_rule_send_removed(struct rule *rule, uint8_t reason)
     struct ofputil_flow_removed fr;
     long long int used;
 
-    if (ofproto_rule_is_hidden(rule) ||
-        !(rule->flags & OFPUTIL_FF_SEND_FLOW_REM)) {
+    if (rule_is_hidden(rule) || !(rule->flags & OFPUTIL_FF_SEND_FLOW_REM)) {
         return;
     }
 
@@ -4842,7 +4811,7 @@ ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
 {
     enum nx_flow_monitor_flags update;
 
-    if (ofproto_rule_is_hidden(rule)) {
+    if (rule_is_hidden(rule)) {
         return;
     }
 
@@ -5032,8 +5001,6 @@ handle_flow_monitor_cancel(struct ofconn *ofconn, const struct ofp_header *oh)
 /* Meters implementation.
  *
  * Meter table entry, indexed by the OpenFlow meter_id.
- * These are always dynamically allocated to allocate enough space for
- * the bands.
  * 'created' is used to compute the duration for meter stats.
  * 'list rules' is needed so that we can delete the dependent rules when the
  * meter table entry is deleted.
@@ -5065,6 +5032,18 @@ get_provider_meter_id(const struct ofproto *ofproto, uint32_t of_meter_id)
     return UINT32_MAX;
 }
 
+/* Finds the meter invoked by 'rule''s actions and adds 'rule' to the meter's
+ * list of rules. */
+static void
+meter_insert_rule(struct rule *rule)
+{
+    const struct rule_actions *a = rule_get_actions(rule);
+    uint32_t meter_id = ofpacts_get_meter(a->ofpacts, a->ofpacts_len);
+    struct meter *meter = rule->ofproto->meters[meter_id];
+
+    list_insert(&meter->rules, &rule->meter_list_node);
+}
+
 static void
 meter_update(struct meter *meter, const struct ofputil_meter_config *config)
 {
@@ -5352,40 +5331,38 @@ handle_meter_request(struct ofconn *ofconn, const struct ofp_header *request,
     return 0;
 }
 
-/* If the group exists, this function increments the groups's reference count.
- *
- * Make sure to call ofproto_group_unref() after no longer needing to maintain
- * a reference to the group. */
-bool
-ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
-                     struct ofgroup **group)
+static bool
+ofproto_group_lookup__(const struct ofproto *ofproto, uint32_t group_id,
+                       struct ofgroup **group)
+    OVS_REQ_RDLOCK(ofproto->groups_rwlock)
 {
-    ovs_rwlock_rdlock(&ofproto->groups_rwlock);
     HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
                              hash_int(group_id, 0), &ofproto->groups) {
         if ((*group)->group_id == group_id) {
-            ofproto_group_ref(*group);
-            ovs_rwlock_unlock(&ofproto->groups_rwlock);
             return true;
         }
     }
-    ovs_rwlock_unlock(&ofproto->groups_rwlock);
+
     return false;
 }
 
-static bool
-ofproto_group_write_lookup(const struct ofproto *ofproto, uint32_t group_id,
-                           struct ofgroup **group)
-    OVS_ACQUIRES(ofproto->groups_rwlock)
+/* If the group exists, this function increments the groups's reference count.
+ *
+ * Make sure to call ofproto_group_unref() after no longer needing to maintain
+ * a reference to the group. */
+bool
+ofproto_group_lookup(const struct ofproto *ofproto, uint32_t group_id,
+                     struct ofgroup **group)
 {
-    ovs_rwlock_wrlock(&ofproto->groups_rwlock);
-    HMAP_FOR_EACH_IN_BUCKET (*group, hmap_node,
-                             hash_int(group_id, 0), &ofproto->groups) {
-        if ((*group)->group_id == group_id) {
-            return true;
-        }
+    bool found;
+
+    ovs_rwlock_rdlock(&ofproto->groups_rwlock);
+    found = ofproto_group_lookup__(ofproto, group_id, group);
+    if (found) {
+        ofproto_group_ref(*group);
     }
-    return false;
+    ovs_rwlock_unlock(&ofproto->groups_rwlock);
+    return found;
 }
 
 static bool
@@ -5632,19 +5609,9 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
     return error;
 }
 
-/* Implements OFPGC11_ADD
- * in which no matching flow already exists in the flow table.
- *
- * Adds the flow specified by 'ofm', which is followed by 'n_actions'
- * ofp_actions, to the ofproto's flow table.  Returns 0 on success, an OpenFlow
- * error code on failure, or OFPROTO_POSTPONE if the operation cannot be
- * initiated now but may be retried later.
- *
- * Upon successful return, takes ownership of 'fm->ofpacts'.  On failure,
- * ownership remains with the caller.
- *
- * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id,
- * if any. */
+/* Implements the OFPGC11_ADD operation specified by 'gm', adding a group to
+ * 'ofproto''s group table.  Returns 0 on success or an OpenFlow error code on
+ * failure. */
 static enum ofperr
 add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
 {
@@ -5690,15 +5657,12 @@ add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
     return error;
 }
 
-/* Implements OFPFC_MODIFY.  Returns 0 on success or an OpenFlow error code on
- * failure.
+/* Implements OFPGC11_MODIFY.  Returns 0 on success or an OpenFlow error code
+ * on failure.
  *
  * Note that the group is re-created and then replaces the old group in
  * ofproto's ofgroup hash map. Thus, the group is never altered while users of
- * the xlate module hold a pointer to the group.
- *
- * 'ofconn' is used to retrieve the packet buffer specified in fm->buffer_id,
- * if any. */
+ * the xlate module hold a pointer to the group. */
 static enum ofperr
 modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
 {
@@ -5712,7 +5676,8 @@ modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
 
     retiring = new_ofgroup;
 
-    if (!ofproto_group_write_lookup(ofproto, gm->group_id, &ofgroup)) {
+    ovs_rwlock_wrlock(&ofproto->groups_rwlock);
+    if (!ofproto_group_lookup__(ofproto, gm->group_id, &ofgroup)) {
         error = OFPERR_OFPGMFC_UNKNOWN_GROUP;
         goto out;
     }
@@ -5759,6 +5724,7 @@ delete_group__(struct ofproto *ofproto, struct ofgroup *ofgroup)
     /* Delete all flow entries containing this group in a group action */
     match_init_catchall(&match);
     flow_mod_init(&fm, &match, 0, NULL, 0, OFPFC_DELETE);
+    fm.delete_reason = OFPRR_GROUP_DELETE;
     fm.out_group = ofgroup->group_id;
     handle_flow_mod__(ofproto, NULL, &fm, NULL);
 
@@ -5769,7 +5735,7 @@ delete_group__(struct ofproto *ofproto, struct ofgroup *ofgroup)
     ofproto_group_unref(ofgroup);
 }
 
-/* Implements OFPGC_DELETE. */
+/* Implements OFPGC11_DELETE. */
 static void
 delete_group(struct ofproto *ofproto, uint32_t group_id)
 {
@@ -6282,12 +6248,10 @@ ofopgroup_complete(struct ofopgroup *group)
 
               - The operation's only effect was to update rule->modified. */
         if (!(op->error
-              || ofproto_rule_is_hidden(rule)
+              || rule_is_hidden(rule)
               || (op->type == OFOPERATION_MODIFY
-                  && op->actions
+                  && !op->actions
                   && rule->flow_cookie == op->flow_cookie))) {
-            /* Check that we can just cast from ofoperation_type to
-             * nx_flow_update_event. */
             enum nx_flow_update_event event_type;
 
             switch (op->type) {
@@ -6314,6 +6278,7 @@ ofopgroup_complete(struct ofopgroup *group)
 
         rule->pending = NULL;
 
+        ovs_assert(!op->error || op->type == OFOPERATION_ADD);
         switch (op->type) {
         case OFOPERATION_ADD:
             if (!op->error) {
@@ -6338,42 +6303,22 @@ ofopgroup_complete(struct ofopgroup *group)
             break;
 
         case OFOPERATION_DELETE:
-            ovs_assert(!op->error);
             ofproto_rule_unref(rule);
             op->rule = NULL;
             break;
 
         case OFOPERATION_MODIFY:
-        case OFOPERATION_REPLACE:
-            if (!op->error) {
-                long long int now = time_msec();
+        case OFOPERATION_REPLACE: {
+            long long now = time_msec();
 
-                ovs_mutex_lock(&rule->mutex);
-                rule->modified = now;
-                if (op->type == OFOPERATION_REPLACE) {
-                    rule->created = now;
-                }
-                ovs_mutex_unlock(&rule->mutex);
-            } else {
-                ofproto_rule_change_cookie(ofproto, rule, op->flow_cookie);
-                ovs_mutex_lock(&rule->mutex);
-                rule->idle_timeout = op->idle_timeout;
-                rule->hard_timeout = op->hard_timeout;
-                ovs_mutex_unlock(&rule->mutex);
-                if (op->actions) {
-                    const struct rule_actions *old_actions;
-
-                    ovs_mutex_lock(&rule->mutex);
-                    old_actions = rule_get_actions(rule);
-                    ovsrcu_set(&rule->actions, op->actions);
-                    ovs_mutex_unlock(&rule->mutex);
-
-                    op->actions = NULL;
-                    rule_actions_destroy(old_actions);
-                }
-                rule->flags = op->flags;
+            ovs_mutex_lock(&rule->mutex);
+            rule->modified = now;
+            if (op->type == OFOPERATION_REPLACE) {
+                rule->created = now;
             }
+            ovs_mutex_unlock(&rule->mutex);
             break;
+        }
 
         default:
             OVS_NOT_REACHED();
@@ -6465,19 +6410,12 @@ ofoperation_destroy(struct ofoperation *op)
  * If 'error' is 0, indicating success, the operation will be committed
  * permanently to the flow table.
  *
- * If 'error' is nonzero, then generally the operation will be rolled back:
- *
- *   - If 'op' is an "add flow" operation, ofproto removes the new rule or
- *     restores the original rule.  The caller must have uninitialized any
- *     derived state in the new rule, as in step 5 of in the "Life Cycle" in
- *     ofproto/ofproto-provider.h.  ofoperation_complete() performs steps 6 and
- *     and 7 for the new rule, calling its ->rule_dealloc() function.
- *
- *   - If 'op' is a "modify flow" operation, ofproto restores the original
- *     actions.
- *
- *   - 'op' must not be a "delete flow" operation.  Removing a rule is not
- *     allowed to fail.  It must always succeed.
+ * Flow modifications and deletions must always succeed.  Flow additions may
+ * fail, indicated by nonzero 'error'.  If an "add flow" operation fails, this
+ * function removes the new rule.  The caller must have uninitialized any
+ * derived state in the new rule, as in step 5 of in the "Life Cycle" in
+ * ofproto/ofproto-provider.h.  ofoperation_complete() performs steps 6 and and
+ * 7 for the new rule, calling its ->rule_dealloc() function.
  *
  * Please see the large comment in ofproto/ofproto-provider.h titled
  * "Asynchronous Operation Support" for more information. */
@@ -6487,7 +6425,7 @@ ofoperation_complete(struct ofoperation *op, enum ofperr error)
     struct ofopgroup *group = op->group;
 
     ovs_assert(group->n_running > 0);
-    ovs_assert(!error || op->type != OFOPERATION_DELETE);
+    ovs_assert(!error || op->type == OFOPERATION_ADD);
 
     op->error = error;
     if (!--group->n_running && !list_is_empty(&group->ofproto_node)) {
@@ -6804,6 +6742,9 @@ oftable_init(struct oftable *table)
     classifier_set_prefix_fields(&table->cls, default_prefix_fields,
                                  ARRAY_SIZE(default_prefix_fields));
     fat_rwlock_unlock(&table->cls.rwlock);
+
+    atomic_init(&table->n_matched, 0);
+    atomic_init(&table->n_missed, 0);
 }
 
 /* Destroys 'table', including its classifier and eviction groups.
@@ -6933,40 +6874,6 @@ oftable_remove_rule(struct rule *rule)
 {
     oftable_remove_rule__(rule->ofproto, rule);
 }
-
-/* Inserts 'rule' into its oftable, which must not already contain any rule for
- * the same cls_rule. */
-static void
-oftable_insert_rule(struct rule *rule)
-    OVS_REQUIRES(ofproto_mutex)
-{
-    struct ofproto *ofproto = rule->ofproto;
-    struct oftable *table = &ofproto->tables[rule->table_id];
-    const struct rule_actions *actions;
-    bool may_expire;
-
-    ovs_mutex_lock(&rule->mutex);
-    may_expire = rule->hard_timeout || rule->idle_timeout;
-    ovs_mutex_unlock(&rule->mutex);
-
-    if (may_expire) {
-        list_insert(&ofproto->expirable, &rule->expirable);
-    }
-
-    cookies_insert(ofproto, rule);
-
-    actions = rule_get_actions(rule);
-    if (actions->provider_meter_id != UINT32_MAX) {
-        uint32_t meter_id = ofpacts_get_meter(actions->ofpacts,
-                                              actions->ofpacts_len);
-        struct meter *meter = ofproto->meters[meter_id];
-        list_insert(&meter->rules, &rule->meter_list_node);
-    }
-    fat_rwlock_wrlock(&table->cls.rwlock);
-    classifier_insert(&table->cls, CONST_CAST(struct cls_rule *, &rule->cr));
-    fat_rwlock_unlock(&table->cls.rwlock);
-    eviction_group_add_rule(rule);
-}
 \f
 /* unixctl commands. */
 
@@ -7052,6 +6959,8 @@ ofproto_get_vlan_usage(struct ofproto *ofproto, unsigned long int *vlan_bitmap)
         }
         fat_rwlock_unlock(&oftable->cls.rwlock);
     }
+
+    cls_rule_destroy(&target);
 }
 
 /* Returns true if new VLANs have come into use by the flow table since the