ofproto: Ignore generation ID for role change to "equal".
[cascardo/ovs.git] / ofproto / ofproto.c
index cf5fa34..a255203 100644 (file)
@@ -83,10 +83,12 @@ static void oftable_set_name(struct oftable *, const char *name);
 
 static enum ofperr evict_rules_from_table(struct oftable *)
     OVS_REQUIRES(ofproto_mutex);
-static void oftable_disable_eviction(struct oftable *);
+static void oftable_disable_eviction(struct oftable *)
+    OVS_REQUIRES(ofproto_mutex);
 static void oftable_enable_eviction(struct oftable *,
                                     const struct mf_subfield *fields,
-                                    size_t n_fields);
+                                    size_t n_fields)
+    OVS_REQUIRES(ofproto_mutex);
 
 /* 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
@@ -377,6 +379,8 @@ ofproto_init(const struct shash *iface_hints)
     for (i = 0; i < n_ofproto_classes; i++) {
         ofproto_classes[i]->init(&init_ofp_ports);
     }
+
+    ofproto_unixctl_init();
 }
 
 /* 'type' should be a normalized datapath type, as returned by
@@ -505,8 +509,6 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
 
     *ofprotop = NULL;
 
-    ofproto_unixctl_init();
-
     datapath_type = ofproto_normalize_type(datapath_type);
     class = ofproto_class_find__(datapath_type);
     if (!class) {
@@ -560,8 +562,7 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
     hmap_init(&ofproto->groups);
     ovs_mutex_unlock(&ofproto_mutex);
     ofproto->ogf.types = 0xf;
-    ofproto->ogf.capabilities = OFPGFC_CHAINING | OFPGFC_SELECT_LIVENESS |
-                                OFPGFC_SELECT_WEIGHT;
+    ofproto->ogf.capabilities = OFPGFC_SELECT_LIVENESS | OFPGFC_SELECT_WEIGHT;
     for (i = 0; i < 4; i++) {
         ofproto->ogf.max_groups[i] = OFPG_MAX;
         ofproto->ogf.ofpacts[i] = (UINT64_C(1) << N_OFPACTS) - 1;
@@ -1419,20 +1420,18 @@ ofproto_configure_table(struct ofproto *ofproto, int table_id,
         return;
     }
 
-    if (s->groups) {
-        oftable_enable_eviction(table, s->groups, s->n_groups);
-    } else {
-        oftable_disable_eviction(table);
-    }
-
-    table->max_flows = s->max_flows;
-
     if (classifier_set_prefix_fields(&table->cls,
                                      s->prefix_fields, s->n_prefix_fields)) {
         /* XXX: Trigger revalidation. */
     }
 
     ovs_mutex_lock(&ofproto_mutex);
+    if (s->groups) {
+        oftable_enable_eviction(table, s->groups, s->n_groups);
+    } else {
+        oftable_disable_eviction(table);
+    }
+    table->max_flows = s->max_flows;
     evict_rules_from_table(table);
     ovs_mutex_unlock(&ofproto_mutex);
 }
@@ -1530,7 +1529,6 @@ ofproto_destroy__(struct ofproto *ofproto)
     struct oftable *table;
 
     destroy_rule_executes(ofproto);
-    delete_group(ofproto, OFPG_ALL);
 
     guarded_list_destroy(&ofproto->rule_executes);
     ovs_rwlock_destroy(&ofproto->groups_rwlock);
@@ -3433,9 +3431,34 @@ update_port_config(struct ofconn *ofconn, struct ofport *port,
 }
 
 static enum ofperr
-handle_port_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+port_mod_start(struct ofconn *ofconn, struct ofputil_port_mod *pm,
+               struct ofport **port)
 {
     struct ofproto *p = ofconn_get_ofproto(ofconn);
+
+    *port = ofproto_get_port(p, pm->port_no);
+    if (!*port) {
+        return OFPERR_OFPPMFC_BAD_PORT;
+    }
+    if (!eth_addr_equals((*port)->pp.hw_addr, pm->hw_addr)) {
+        return OFPERR_OFPPMFC_BAD_HW_ADDR;
+    }
+    return 0;
+}
+
+static void
+port_mod_finish(struct ofconn *ofconn, struct ofputil_port_mod *pm,
+                struct ofport *port)
+{
+    update_port_config(ofconn, port, pm->config, pm->mask);
+    if (pm->advertise) {
+        netdev_set_advertisements(port->netdev, pm->advertise);
+    }
+}
+
+static enum ofperr
+handle_port_mod(struct ofconn *ofconn, const struct ofp_header *oh)
+{
     struct ofputil_port_mod pm;
     struct ofport *port;
     enum ofperr error;
@@ -3450,18 +3473,11 @@ handle_port_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         return error;
     }
 
-    port = ofproto_get_port(p, pm.port_no);
-    if (!port) {
-        return OFPERR_OFPPMFC_BAD_PORT;
-    } else if (!eth_addr_equals(port->pp.hw_addr, pm.hw_addr)) {
-        return OFPERR_OFPPMFC_BAD_HW_ADDR;
-    } else {
-        update_port_config(ofconn, port, pm.config, pm.mask);
-        if (pm.advertise) {
-            netdev_set_advertisements(port->netdev, pm.advertise);
-        }
+    error = port_mod_start(ofconn, &pm, &port);
+    if (!error) {
+        port_mod_finish(ofconn, &pm, port);
     }
-    return 0;
+    return error;
 }
 
 static enum ofperr
@@ -4388,12 +4404,6 @@ evict_rules_from_table(struct oftable *table)
     return error;
 }
 
-static bool
-is_conjunction(const struct ofpact *ofpacts, size_t ofpacts_len)
-{
-    return ofpacts_len > 0 && ofpacts->type == OFPACT_CONJUNCTION;
-}
-
 static void
 get_conjunctions(const struct ofputil_flow_mod *fm,
                  struct cls_conjunction **conjsp, size_t *n_conjsp)
@@ -4402,23 +4412,29 @@ get_conjunctions(const struct ofputil_flow_mod *fm,
     struct cls_conjunction *conjs = NULL;
     int n_conjs = 0;
 
-    if (is_conjunction(fm->ofpacts, fm->ofpacts_len)) {
-        const struct ofpact *ofpact;
-        int i;
-
-        n_conjs = 0;
-        OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+    const struct ofpact *ofpact;
+    OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
+        if (ofpact->type == OFPACT_CONJUNCTION) {
             n_conjs++;
+        } else if (ofpact->type != OFPACT_NOTE) {
+            /* "conjunction" may appear with "note" actions but not with any
+             * other type of actions. */
+            ovs_assert(!n_conjs);
+            break;
         }
+    }
+    if (n_conjs) {
+        int i = 0;
 
         conjs = xzalloc(n_conjs * sizeof *conjs);
-        i = 0;
         OFPACT_FOR_EACH (ofpact, fm->ofpacts, fm->ofpacts_len) {
-            struct ofpact_conjunction *oc = ofpact_get_CONJUNCTION(ofpact);
-            conjs[i].clause = oc->clause;
-            conjs[i].n_clauses = oc->n_clauses;
-            conjs[i].id = oc->id;
-            i++;
+            if (ofpact->type == OFPACT_CONJUNCTION) {
+                struct ofpact_conjunction *oc = ofpact_get_CONJUNCTION(ofpact);
+                conjs[i].clause = oc->clause;
+                conjs[i].n_clauses = oc->n_clauses;
+                conjs[i].id = oc->id;
+                i++;
+            }
         }
     }
 
@@ -5239,7 +5255,8 @@ handle_role_request(struct ofconn *ofconn, const struct ofp_header *oh)
     }
 
     if (request.role != OFPCR12_ROLE_NOCHANGE) {
-        if (request.have_generation_id
+        if (request.role != OFPCR12_ROLE_EQUAL
+            && request.have_generation_id
             && !ofconn_set_master_election_id(ofconn, request.generation_id)) {
                 return OFPERR_OFPRRFC_STALE;
         }
@@ -6486,6 +6503,16 @@ delete_group(struct ofproto *ofproto, uint32_t group_id)
     ovs_rwlock_unlock(&ofproto->groups_rwlock);
 }
 
+/* Delete all groups from 'ofproto'.
+ *
+ * This is intended for use within an ofproto provider's 'destruct'
+ * function. */
+void
+ofproto_group_delete_all(struct ofproto *ofproto)
+{
+    delete_group(ofproto, OFPG_ALL);
+}
+
 static enum ofperr
 handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
 {
@@ -6668,17 +6695,16 @@ do_bundle_flow_mod_finish(struct ofproto *ofproto, struct ofputil_flow_mod *fm,
  * possible.  No visible changes were made, so rollback is minimal (remove
  * added invisible rules, restore visibility of rules marked for removal).
  *
- * 3. Bump the version visible to lookups.
- *
- * 4. Finish: Insert replacement rules to the ofproto provider. Remove replaced
- * and deleted rules from ofproto data structures, and Schedule postponed
- * removal of deleted rules from the classifier.  Send notifications, buffered
- * packets, etc.
+ * 3. Finish: Make the changes visible for lookups. Insert replacement rules to
+ * the ofproto provider. Remove replaced and deleted rules from ofproto data
+ * structures, and Schedule postponed removal of deleted rules from the
+ * classifier.  Send notifications, buffered packets, etc.
  */
 static enum ofperr
 do_bundle_commit(struct ofconn *ofconn, uint32_t id, uint16_t flags)
 {
     struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+    cls_version_t visible_version = ofproto->tables_version;
     struct ofp_bundle *bundle;
     struct ofp_bundle_entry *be;
     enum ofperr error;
@@ -6691,23 +6717,42 @@ do_bundle_commit(struct ofconn *ofconn, uint32_t id, uint16_t flags)
     if (bundle->flags != flags) {
         error = OFPERR_OFPBFC_BAD_FLAGS;
     } else {
+        bool prev_is_port_mod = false;
+
         error = 0;
         ovs_mutex_lock(&ofproto_mutex);
 
         /* 1. Begin. */
         LIST_FOR_EACH (be, node, &bundle->msg_list) {
             if (be->type == OFPTYPE_PORT_MOD) {
-                /* Not supported yet. */
-                error = OFPERR_OFPBFC_MSG_FAILED;
+                /* Our port mods are not atomic. */
+                if (flags & OFPBF_ATOMIC) {
+                    error = OFPERR_OFPBFC_MSG_FAILED;
+                } else {
+                    prev_is_port_mod = true;
+                    error = port_mod_start(ofconn, &be->pm, &be->port);
+                }
             } else if (be->type == OFPTYPE_FLOW_MOD) {
+                /* Flow mods between port mods are applied as a single
+                 * version, but the versions are published only after
+                 * we know the commit is successful. */
+                if (prev_is_port_mod) {
+                    ++ofproto->tables_version;
+                }
+                prev_is_port_mod = false;
                 error = do_bundle_flow_mod_start(ofproto, &be->fm, be);
             } else {
                 OVS_NOT_REACHED();
             }
             if (error) {
                 break;
+            } else {
+                /* Store the version in which the changes should take
+                 * effect. */
+                be->version = ofproto->tables_version + 1;
             }
         }
+
         if (error) {
             /* Send error referring to the original message. */
             if (error) {
@@ -6720,24 +6765,38 @@ do_bundle_commit(struct ofconn *ofconn, uint32_t id, uint16_t flags)
                 if (be->type == OFPTYPE_FLOW_MOD) {
                     do_bundle_flow_mod_revert(ofproto, &be->fm, be);
                 }
+                /* Nothing needs to be reverted for a port mod. */
             }
         } else {
-            /* 3. Bump the version.  This makes all the changes in the bundle
-             * visible to the lookups at once.  For this to work an upcall must
-             * read the tables_version once at the beginning and keep using the
-             * same version number for the whole duration of the upcall
-             * processing. */
-            ofproto_bump_tables_version(ofproto);
-
             /* 4. Finish. */
             LIST_FOR_EACH (be, node, &bundle->msg_list) {
+                /* Bump the lookup version to the one of the current message.
+                 * This makes all the changes in the bundle at this version
+                 * visible to lookups at once. */
+                if (visible_version < be->version) {
+                    visible_version = be->version;
+                    ofproto->ofproto_class->set_tables_version(
+                        ofproto, visible_version);
+                }
                 if (be->type == OFPTYPE_FLOW_MOD) {
                     struct flow_mod_requester req = { ofconn, be->ofp_msg };
 
                     do_bundle_flow_mod_finish(ofproto, &be->fm, &req, be);
+                } else if (be->type == OFPTYPE_PORT_MOD) {
+                    /* Perform the actual port mod. This is not atomic, i.e.,
+                     * the effects will be immediately seen by upcall
+                     * processing regardless of the lookup version.  It should
+                     * be noted that port configuration changes can originate
+                     * also from OVSDB changes asynchronously to all upcall
+                     * processing. */
+                    port_mod_finish(ofconn, &be->pm, be->port);
                 }
             }
         }
+
+        /* Reset the tables_version. */
+        ofproto->tables_version = visible_version;
+
         ofmonitor_flush(ofproto->connmgr);
         ovs_mutex_unlock(&ofproto_mutex);
 
@@ -7381,7 +7440,9 @@ static void
 oftable_destroy(struct oftable *table)
 {
     ovs_assert(classifier_is_empty(&table->cls));
+    ovs_mutex_lock(&ofproto_mutex);
     oftable_disable_eviction(table);
+    ovs_mutex_unlock(&ofproto_mutex);
     classifier_destroy(&table->cls);
     free(table->name);
 }