[ovs-dev] [PATCH ovn v2 5/5] [RFC] First OpenFlow flows generated by ovn-controller.

Ben Pfaff blp at nicira.com
Thu Apr 23 05:09:53 UTC 2015


The flows are just dumped to the ovn-controller log so far.

To see the same thing I'm seeing, start an OVN sandbox environment with
"make sandbox SANDBOXFLAGS=--ovn", then inside the shell, run:

ovn-nbctl lswitch-add br0
ovn-nbctl lport-add br0 eth0
ovs-vsctl add-port br-int eth0 -- set interface eth0 external-ids:iface-id=eth0

and look through sandbox/ovn-controller.log.

There's a ton of cleanup, documentation, and refinement to do on this
commit, hence the RFC (and no sign-off yet), but it feels like a milestone
to me, so I'm posting it anyway.
---
 ovn/controller/automake.mk      |   4 +-
 ovn/controller/ovn-controller.c |   4 +
 ovn/controller/pipeline.c       | 366 ++++++++++++++++++++++++++++++++++++++++
 ovn/controller/pipeline.h       |  26 +++
 ovn/lib/actions.c               | 217 ++++++++++++++++++++++++
 ovn/lib/actions.h               |  40 +++++
 ovn/lib/automake.mk             |   2 +
 ovn/lib/expr.c                  | 165 ++++++++++++++++--
 ovn/lib/expr.h                  |  18 +-
 ovn/lib/lex.c                   |  26 +++
 ovn/lib/lex.h                   |   2 +
 ovn/northd/ovn-northd.c         |  90 +++++++---
 ovn/ovn-sb.ovsschema            |   8 +-
 ovn/ovn-sb.xml                  |  10 +-
 14 files changed, 932 insertions(+), 46 deletions(-)
 create mode 100644 ovn/controller/pipeline.c
 create mode 100644 ovn/controller/pipeline.h
 create mode 100644 ovn/lib/actions.c
 create mode 100644 ovn/lib/actions.h

diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk
index 4a266da..51c73be 100644
--- a/ovn/controller/automake.mk
+++ b/ovn/controller/automake.mk
@@ -5,7 +5,9 @@ ovn_controller_ovn_controller_SOURCES = \
 	ovn/controller/chassis.c \
 	ovn/controller/chassis.h \
 	ovn/controller/ovn-controller.c \
-	ovn/controller/ovn-controller.h
+	ovn/controller/ovn-controller.h \
+	ovn/controller/pipeline.c \
+	ovn/controller/pipeline.h
 ovn_controller_ovn_controller_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la
 man_MANS += ovn/controller/ovn-controller.8
 EXTRA_DIST += ovn/controller/ovn-controller.8.xml
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 44a4d5e..cfc562c 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -40,6 +40,7 @@
 #include "ovn-controller.h"
 #include "bindings.h"
 #include "chassis.h"
+#include "pipeline.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -153,6 +154,7 @@ main(int argc, char *argv[])
 
     ctx.ovnsb_idl = ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class,
                                      true, true);
+    pipeline_init(&ctx);
 
     get_initial_snapshot(ctx.ovnsb_idl);
 
@@ -179,6 +181,7 @@ main(int argc, char *argv[])
 
         chassis_run(&ctx);
         bindings_run(&ctx);
+        pipeline_run(&ctx);
         unixctl_server_run(unixctl);
 
         unixctl_server_wait(unixctl);
@@ -192,6 +195,7 @@ main(int argc, char *argv[])
     }
 
     unixctl_server_destroy(unixctl);
+    pipeline_destroy(&ctx);
     bindings_destroy(&ctx);
     chassis_destroy(&ctx);
 
diff --git a/ovn/controller/pipeline.c b/ovn/controller/pipeline.c
new file mode 100644
index 0000000..cfcdc38
--- /dev/null
+++ b/ovn/controller/pipeline.c
@@ -0,0 +1,366 @@
+/* Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "pipeline.h"
+#include "dynamic-string.h"
+#include "ofp-actions.h"
+#include "ofpbuf.h"
+#include "openvswitch/vlog.h"
+#include "ovn/controller/ovn-controller.h"
+#include "ovn/lib/actions.h"
+#include "ovn/lib/expr.h"
+#include "ovn/lib/ovn-sb-idl.h"
+#include "simap.h"
+
+VLOG_DEFINE_THIS_MODULE(pipeline);
+
+/* Symbol table. */
+
+/* Contains "struct expr_symbol"s for fields supported by OVN pipeline. */
+static struct shash symtab;
+
+static void
+symtab_init(void)
+{
+    shash_init(&symtab);
+
+    /* Reserve a pair of registers for the logical inport and outport.  A full
+     * 32-bit register each is bigger than we need, but the expression code
+     * doesn't yet support string fields that occupy less than a full OXM. */
+    expr_symtab_add_string(&symtab, "inport", MFF_REG6, NULL);
+    expr_symtab_add_string(&symtab, "outport", MFF_REG7, NULL);
+
+    /* Registers.  We omit the registers that would otherwise overlap 'inport'
+     * and 'outport'. */
+    expr_symtab_add_field(&symtab, "xreg0", MFF_XREG0, NULL, false);
+    expr_symtab_add_field(&symtab, "xreg1", MFF_XREG1, NULL, false);
+    expr_symtab_add_field(&symtab, "xreg2", MFF_XREG2, NULL, false);
+
+    expr_symtab_add_subfield(&symtab, "reg0", NULL, "xreg0[32..63]");
+    expr_symtab_add_subfield(&symtab, "reg1", NULL, "xreg0[0..31]");
+    expr_symtab_add_subfield(&symtab, "reg2", NULL, "xreg1[32..63]");
+    expr_symtab_add_subfield(&symtab, "reg3", NULL, "xreg1[0..31]");
+    expr_symtab_add_subfield(&symtab, "reg4", NULL, "xreg2[32..63]");
+    expr_symtab_add_subfield(&symtab, "reg5", NULL, "xreg2[0..31]");
+
+    /* Data fields. */
+
+    expr_symtab_add_field(&symtab, "eth.src", MFF_ETH_SRC, NULL, false);
+    expr_symtab_add_field(&symtab, "eth.dst", MFF_ETH_DST, NULL, false);
+    expr_symtab_add_field(&symtab, "eth.type", MFF_ETH_TYPE, NULL, true);
+
+    expr_symtab_add_field(&symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false);
+    expr_symtab_add_predicate(&symtab, "vlan.present", "vlan.tci[12]");
+    expr_symtab_add_subfield(&symtab, "vlan.pcp", "vlan.present",
+                             "vlan.tci[13..15]");
+    expr_symtab_add_subfield(&symtab, "vlan.vid", "vlan.present",
+                             "vlan.tci[0..11]");
+
+    expr_symtab_add_predicate(&symtab, "ip4", "eth.type == 0x800");
+    expr_symtab_add_predicate(&symtab, "ip6", "eth.type == 0x86dd");
+    expr_symtab_add_predicate(&symtab, "ip", "ip4 || ip6");
+    expr_symtab_add_field(&symtab, "ip.proto", MFF_IP_PROTO, "ip", true);
+    expr_symtab_add_field(&symtab, "ip.dscp", MFF_IP_DSCP, "ip", false);
+    expr_symtab_add_field(&symtab, "ip.ecn", MFF_IP_ECN, "ip", false);
+    expr_symtab_add_field(&symtab, "ip.ttl", MFF_IP_TTL, "ip", false);
+
+    expr_symtab_add_field(&symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false);
+    expr_symtab_add_field(&symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false);
+
+    expr_symtab_add_predicate(&symtab, "icmp4", "ip4 && ip.proto == 1");
+    expr_symtab_add_field(&symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4",
+              false);
+    expr_symtab_add_field(&symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
+              false);
+
+    expr_symtab_add_field(&symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
+    expr_symtab_add_field(&symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
+    expr_symtab_add_field(&symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false);
+
+    expr_symtab_add_predicate(&symtab, "icmp6", "ip6 && ip.proto == 58");
+    expr_symtab_add_field(&symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6",
+                          true);
+    expr_symtab_add_field(&symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6",
+                          true);
+
+    expr_symtab_add_predicate(&symtab, "icmp", "icmp4 || icmp6");
+
+    expr_symtab_add_field(&symtab, "ip.frag", MFF_IP_FRAG, "ip", false);
+    expr_symtab_add_predicate(&symtab, "ip.is_frag", "ip.frag[0]");
+    expr_symtab_add_predicate(&symtab, "ip.later_frag", "ip.frag[1]");
+    expr_symtab_add_predicate(&symtab, "ip.first_frag",
+                              "ip.is_frag && !ip.later_frag");
+
+    expr_symtab_add_predicate(&symtab, "arp", "eth.type == 0x806");
+    expr_symtab_add_field(&symtab, "arp.op", MFF_ARP_OP, "arp", false);
+    expr_symtab_add_field(&symtab, "arp.spa", MFF_ARP_SPA, "arp", false);
+    expr_symtab_add_field(&symtab, "arp.sha", MFF_ARP_SHA, "arp", false);
+    expr_symtab_add_field(&symtab, "arp.tpa", MFF_ARP_TPA, "arp", false);
+    expr_symtab_add_field(&symtab, "arp.tha", MFF_ARP_THA, "arp", false);
+
+    expr_symtab_add_predicate(&symtab, "nd",
+                              "icmp6.type == {135, 136} && icmp6.code == 0");
+    expr_symtab_add_field(&symtab, "nd.target", MFF_ND_TARGET, "nd", false);
+    expr_symtab_add_field(&symtab, "nd.sll", MFF_ND_SLL,
+              "nd && icmp6.type == 135", false);
+    expr_symtab_add_field(&symtab, "nd.tll", MFF_ND_TLL,
+              "nd && icmp6.type == 136", false);
+
+    expr_symtab_add_predicate(&symtab, "tcp", "ip.proto == 6");
+    expr_symtab_add_field(&symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
+    expr_symtab_add_field(&symtab, "tcp.dst", MFF_TCP_DST, "tcp", false);
+    expr_symtab_add_field(&symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false);
+
+    expr_symtab_add_predicate(&symtab, "udp", "ip.proto == 17");
+    expr_symtab_add_field(&symtab, "udp.src", MFF_UDP_SRC, "udp", false);
+    expr_symtab_add_field(&symtab, "udp.dst", MFF_UDP_DST, "udp", false);
+
+    expr_symtab_add_predicate(&symtab, "sctp", "ip.proto == 132");
+    expr_symtab_add_field(&symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false);
+    expr_symtab_add_field(&symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false);
+}
+
+/* Logical datapaths and logical port numbers. */
+
+/* A logical datapath.
+ *
+ * 'uuid' is the UUID that represents the logical datapath in the OVN_SB
+ * database.
+ *
+ * 'integer' represents the logical datapath as an integer value that is unique
+ * only within the local hypervisor.  Because of its size, this value is more
+ * practical for use in an OpenFlow flow table than a UUID.
+ *
+ * 'ports' maps 'logical_port' names to 'tunnel_key' values in the OVN_SB
+ * Bindings table within the logical datapath. */
+struct logical_datapath {
+    struct hmap_node hmap_node; /* Indexed on 'uuid'. */
+    struct uuid uuid;           /* The logical_datapath's UUID. */
+    uint32_t integer;           /* Locally unique among logical datapaths. */
+    struct simap ports;         /* Logical port name to port number. */
+};
+
+/* Contains "struct logical_datapath"s. */
+static struct hmap logical_datapaths = HMAP_INITIALIZER(&logical_datapaths);
+
+static struct logical_datapath *
+ldp_lookup(const struct uuid *uuid)
+{
+    struct logical_datapath *ldp;
+    HMAP_FOR_EACH_IN_BUCKET (ldp, hmap_node, uuid_hash(uuid),
+                             &logical_datapaths) {
+        if (uuid_equals(&ldp->uuid, uuid)) {
+            return ldp;
+        }
+    }
+    return NULL;
+}
+
+static struct logical_datapath *
+ldp_create(const struct uuid *uuid)
+{
+    static uint32_t next_integer = 1;
+    struct logical_datapath *ldp;
+
+    ldp = xmalloc(sizeof *ldp);
+    hmap_insert(&logical_datapaths, &ldp->hmap_node, uuid_hash(uuid));
+    ldp->uuid = *uuid;
+    ldp->integer = next_integer++;
+    simap_init(&ldp->ports);
+    return ldp;
+}
+
+static void
+ldp_run(struct controller_ctx *ctx)
+{
+    struct logical_datapath *ldp;
+    HMAP_FOR_EACH (ldp, hmap_node, &logical_datapaths) {
+        simap_clear(&ldp->ports);
+    }
+
+    const struct sbrec_bindings *binding;
+    SBREC_BINDINGS_FOR_EACH (binding, ctx->ovnsb_idl) {
+        struct logical_datapath *ldp;
+
+        ldp = ldp_lookup(&binding->logical_datapath);
+        if (!ldp) {
+            ldp = ldp_create(&binding->logical_datapath);
+        }
+
+        simap_put(&ldp->ports, binding->logical_port, binding->tunnel_key);
+    }
+
+    struct logical_datapath *next_ldp;
+    HMAP_FOR_EACH_SAFE (ldp, next_ldp, hmap_node, &logical_datapaths) {
+        if (simap_is_empty(&ldp->ports)) {
+            simap_destroy(&ldp->ports);
+            hmap_remove(&logical_datapaths, &ldp->hmap_node);
+            free(ldp);
+        }
+    }
+}
+
+static void
+ldp_destroy(void)
+{
+    struct logical_datapath *ldp, *next_ldp;
+    HMAP_FOR_EACH_SAFE (ldp, next_ldp, hmap_node, &logical_datapaths) {
+        simap_destroy(&ldp->ports);
+        hmap_remove(&logical_datapaths, &ldp->hmap_node);
+        free(ldp);
+    }
+}
+
+void
+pipeline_init(struct controller_ctx *ctx)
+{
+    symtab_init();
+
+    ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_logical_datapath);
+    ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_table_id);
+    ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_priority);
+    ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_match);
+    ovsdb_idl_add_column(ctx->ovnsb_idl, &sbrec_pipeline_col_actions);
+}
+
+struct ovn_flow {
+    struct hmap_node hmap_node; /* Indexed by 'match'. */
+    struct match match;
+    struct ofpact *ofpacts;
+    size_t ofpacts_len;
+    uint8_t table_id;
+};
+
+static void
+add_ovn_flow(uint8_t table_id, uint16_t priority, const struct match *match,
+             const struct ofpbuf *ofpacts)
+{
+    struct ds s = DS_EMPTY_INITIALIZER;
+    ds_put_format(&s, "table_id=%"PRIu8", ", table_id);
+    ds_put_format(&s, "priority=%"PRIu16", ", priority);
+    match_format(match, &s, OFP_DEFAULT_PRIORITY);
+    ds_put_cstr(&s, ", actions=");
+    ofpacts_format(ofpacts->data, ofpacts->size, &s);
+    VLOG_INFO("%s", ds_cstr(&s));
+    ds_destroy(&s);
+}
+
+void
+pipeline_run(struct controller_ctx *ctx)
+{
+    struct hmap flows = HMAP_INITIALIZER(&flows);
+    uint32_t conj_id_ofs = 1;
+
+    ldp_run(ctx);
+
+    VLOG_INFO("starting run...");
+    const struct sbrec_pipeline *pipeline;
+    SBREC_PIPELINE_FOR_EACH (pipeline, ctx->ovnsb_idl) {
+        const struct logical_datapath *ldp;
+        ldp = ldp_lookup(&pipeline->logical_datapath);
+        if (!ldp) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_INFO_RL(&rl,
+                         "logical flow for unknown logical datapath "UUID_FMT,
+                         UUID_ARGS(&pipeline->logical_datapath));
+            continue;
+        }
+
+        uint64_t ofpacts_stub[64 / 8];
+        struct ofpbuf ofpacts;
+        struct expr *prereqs;
+        char *error;
+
+        ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
+        error = actions_parse_string(pipeline->actions, &symtab, &ldp->ports,
+                                     pipeline->table_id + 16,
+                                     &ofpacts, &prereqs);
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s",
+                         pipeline->actions, error);
+            free(error);
+            continue;
+        }
+
+        struct hmap matches;
+        struct expr *expr;
+
+        expr = expr_parse_string(pipeline->match, &symtab, &error);
+        if (!error) {
+            if (prereqs) {
+                expr = expr_combine(EXPR_T_AND, expr, prereqs);
+                prereqs = NULL;
+            }
+            expr = expr_annotate(expr, &symtab, &error);
+        }
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s",
+                         pipeline->match, error);
+            expr_destroy(prereqs);
+            ofpbuf_uninit(&ofpacts);
+            free(error);
+            continue;
+        }
+
+        expr = expr_simplify(expr);
+        expr = expr_normalize(expr);
+        uint32_t n_conjs = expr_to_matches(expr, &ldp->ports, &matches);
+        expr_destroy(expr);
+
+        struct expr_match *m;
+        HMAP_FOR_EACH (m, hmap_node, &matches) {
+            match_set_metadata(&m->match, htonll(ldp->integer));
+            if (m->match.wc.masks.conj_id) {
+                m->match.flow.conj_id += conj_id_ofs;
+            }
+            if (!m->n) {
+                add_ovn_flow(pipeline->table_id + 16, pipeline->priority,
+                             &m->match, &ofpacts);
+            } else {
+                uint64_t conj_stubs[64 / 8];
+                struct ofpbuf conj;
+
+                ofpbuf_use_stub(&conj, conj_stubs, sizeof conj_stubs);
+                for (int i = 0; i < m->n; i++) {
+                    const struct cls_conjunction *src = &m->conjunctions[i];
+                    struct ofpact_conjunction *dst;
+
+                    dst = ofpact_put_CONJUNCTION(&conj);
+                    dst->id = src->id + conj_id_ofs;
+                    dst->clause = src->clause;
+                    dst->n_clauses = src->n_clauses;
+                }
+                add_ovn_flow(pipeline->table_id + 16, pipeline->priority,
+                             &m->match, &conj);
+                ofpbuf_uninit(&conj);
+            }
+        }
+
+        expr_matches_destroy(&matches);
+        ofpbuf_uninit(&ofpacts);
+        conj_id_ofs += n_conjs;
+    }
+    VLOG_INFO("...done");
+}
+
+void
+pipeline_destroy(struct controller_ctx *ctx OVS_UNUSED)
+{
+    expr_symtab_destroy(&symtab);
+    ldp_destroy();
+}
diff --git a/ovn/controller/pipeline.h b/ovn/controller/pipeline.h
new file mode 100644
index 0000000..d127bf5
--- /dev/null
+++ b/ovn/controller/pipeline.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef OVN_PIPELINE_H
+#define OVN_PIPELINE_H 1
+
+struct controller_ctx;
+
+void pipeline_init(struct controller_ctx *);
+void pipeline_run(struct controller_ctx *);
+void pipeline_destroy(struct controller_ctx *);
+
+#endif /* ovn/pipeline.h */
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
new file mode 100644
index 0000000..266f692
--- /dev/null
+++ b/ovn/lib/actions.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "actions.h"
+#include <stdarg.h>
+#include <stdbool.h>
+#include "compiler.h"
+#include "dynamic-string.h"
+#include "expr.h"
+#include "lex.h"
+#include "ofp-actions.h"
+#include "ofpbuf.h"
+
+/* Context maintained during actions_parse(). */
+struct action_context {
+    /* Input. */
+    struct lexer *lexer;        /* Lexer for pulling more tokens. */
+    const struct shash *symtab; /* Symbol table. */
+    uint8_t table_id;           /* Current logical table. */
+    const struct simap *ports;  /* Map from port name to number. */
+
+    /* State. */
+    char *error;                /* Error, if any, otherwise NULL. */
+
+    /* Output. */
+    struct ofpbuf *ofpacts;     /* Actions. */
+    struct expr *prereqs;       /* Prerequisites to apply to match. */
+};
+
+static bool
+action_error_handle_common(struct action_context *ctx)
+{
+    if (ctx->error) {
+        /* Already have an error, suppress this one since the cascade seems
+         * unlikely to be useful. */
+        return true;
+    } else if (ctx->lexer->token.type == LEX_T_ERROR) {
+        /* The lexer signaled an error.  Nothing at the action level
+         * accepts an error token, so we'll inevitably end up here with some
+         * meaningless parse error.  Report the lexical error instead. */
+        ctx->error = xstrdup(ctx->lexer->token.s);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void OVS_PRINTF_FORMAT(2, 3)
+action_error(struct action_context *ctx, const char *message, ...)
+{
+    if (action_error_handle_common(ctx)) {
+        return;
+    }
+
+    va_list args;
+    va_start(args, message);
+    ctx->error = xvasprintf(message, args);
+    va_end(args);
+}
+
+static void OVS_PRINTF_FORMAT(2, 3)
+action_syntax_error(struct action_context *ctx, const char *message, ...)
+{
+    if (action_error_handle_common(ctx)) {
+        return;
+    }
+
+    struct ds s;
+
+    ds_init(&s);
+    ds_put_cstr(&s, "Syntax error");
+    if (ctx->lexer->token.type == LEX_T_END) {
+        ds_put_cstr(&s, " at end of input");
+    } else if (ctx->lexer->start) {
+        ds_put_format(&s, " at `%.*s'",
+                      (int) (ctx->lexer->input - ctx->lexer->start),
+                      ctx->lexer->start);
+    }
+
+    if (message) {
+        ds_put_char(&s, ' ');
+
+        va_list args;
+        va_start(args, message);
+        ds_put_format_valist(&s, message, args);
+        va_end(args);
+    }
+    ds_put_char(&s, '.');
+
+    ctx->error = ds_steal_cstr(&s);
+}
+
+static void
+parse_set_action(struct action_context *ctx)
+{
+    struct expr *prereqs;
+    char *error;
+
+    error = expr_parse_assignment(ctx->lexer, ctx->symtab, ctx->ports,
+                                  ctx->ofpacts, &prereqs);
+    if (error) {
+        action_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, prereqs);
+}
+
+static void
+emit_resubmit(struct action_context *ctx, uint8_t table_id)
+{
+    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ctx->ofpacts);
+    resubmit->in_port = OFPP_IN_PORT;
+    resubmit->table_id = table_id;
+}
+
+static void
+parse_actions(struct action_context *ctx)
+{
+    /* "drop;" by itself is a valid (empty) set of actions, but it can't be
+     * combined with other actions because that doesn't make sense. */
+    if (ctx->lexer->token.type == LEX_T_ID
+        && !strcmp(ctx->lexer->token.s, "drop")
+        && lexer_lookahead(ctx->lexer) == LEX_T_SEMICOLON) {
+        lexer_get(ctx->lexer);  /* Skip "drop". */
+        lexer_get(ctx->lexer);  /* Skip ";". */
+        if (ctx->lexer->token.type != LEX_T_END) {
+            action_syntax_error(ctx, "expecting end of input");
+        }
+        return;
+    }
+
+    while (ctx->lexer->token.type != LEX_T_END) {
+        if (ctx->lexer->token.type != LEX_T_ID) {
+            action_syntax_error(ctx, NULL);
+            break;
+        }
+
+        enum lex_type lookahead = lexer_lookahead(ctx->lexer);
+        if (lookahead == LEX_T_EQUALS || lookahead == LEX_T_LSQUARE) {
+            parse_set_action(ctx);
+        } else if (lexer_match_id(ctx->lexer, "resubmit")) {
+            emit_resubmit(ctx, ctx->table_id + 1);
+        } else if (lexer_match_id(ctx->lexer, "output")) {
+            emit_resubmit(ctx, 64);
+        } else {
+            action_syntax_error(ctx, "expecting action");
+        }
+        if (!lexer_match(ctx->lexer, LEX_T_SEMICOLON)) {
+            action_syntax_error(ctx, "expecting ';'");
+        }
+        if (ctx->error) {
+            return;
+        }
+    }
+}
+
+char *
+actions_parse(struct lexer *lexer, const struct shash *symtab,
+              const struct simap *ports, uint8_t table_id,
+              struct ofpbuf *ofpacts, struct expr **prereqsp)
+{
+    size_t ofpacts_start = ofpacts->size;
+
+    struct action_context ctx;
+    ctx.lexer = lexer;
+    ctx.symtab = symtab;
+    ctx.ports = ports;
+    ctx.table_id = table_id;
+    ctx.error = NULL;
+    ctx.ofpacts = ofpacts;
+    ctx.prereqs = NULL;
+
+    parse_actions(&ctx);
+
+    if (!ctx.error) {
+        *prereqsp = ctx.prereqs;
+        return NULL;
+    } else {
+        ofpacts->size = ofpacts_start;
+        expr_destroy(ctx.prereqs);
+        *prereqsp = NULL;
+        return ctx.error;
+    }
+}
+
+/* Like actions_parse(), but the actions are taken from 's'. */
+char *
+actions_parse_string(const char *s, const struct shash *symtab,
+                     const struct simap *ports, uint8_t table_id,
+                     struct ofpbuf *ofpacts, struct expr **prereqsp)
+{
+    struct lexer lexer;
+    char *error;
+
+    lexer_init(&lexer, s);
+    lexer_get(&lexer);
+    error = actions_parse(&lexer, symtab, ports, table_id, ofpacts, prereqsp);
+    lexer_destroy(&lexer);
+
+    return error;
+}
diff --git a/ovn/lib/actions.h b/ovn/lib/actions.h
new file mode 100644
index 0000000..b959c4b
--- /dev/null
+++ b/ovn/lib/actions.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_ACTIONS_H
+#define OVN_ACTIONS_H 1
+
+/* OVN actions parsing
+ * ===================
+ *
+ */
+
+#include <stdint.h>
+
+struct expr;
+struct lexer;
+struct ofpbuf;
+struct shash;
+struct simap;
+
+char *actions_parse(struct lexer *, const struct shash *symtab,
+                    const struct simap *ports, uint8_t table_id,
+                    struct ofpbuf *ofpacts, struct expr **prereqsp);
+char *actions_parse_string(const char *s, const struct shash *symtab,
+                           const struct simap *ports, uint8_t table_id,
+                           struct ofpbuf *ofpacts, struct expr **prereqsp);
+
+#endif /* ovn/actions.h */
diff --git a/ovn/lib/automake.mk b/ovn/lib/automake.mk
index 454f2ef..956c83f 100644
--- a/ovn/lib/automake.mk
+++ b/ovn/lib/automake.mk
@@ -4,6 +4,8 @@ ovn_lib_libovn_la_LDFLAGS = \
         -Wl,--version-script=$(top_builddir)/ovn/lib/libovn.sym \
         $(AM_LDFLAGS)
 ovn_lib_libovn_la_SOURCES = \
+	ovn/lib/actions.c \
+	ovn/lib/actions.h \
 	ovn/lib/expr.c \
 	ovn/lib/expr.h \
 	ovn/lib/lex.c \
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
index fb9b1cf..7a43067 100644
--- a/ovn/lib/expr.c
+++ b/ovn/lib/expr.c
@@ -20,6 +20,7 @@
 #include "json.h"
 #include "lex.h"
 #include "match.h"
+#include "ofp-actions.h"
 #include "shash.h"
 #include "simap.h"
 #include "openvswitch/vlog.h"
@@ -562,6 +563,33 @@ expr_constant_width(const union expr_constant *c)
     }
 }
 
+static bool
+type_check(struct expr_context *ctx, const struct expr_field *f,
+           struct expr_constant_set *cs)
+{
+    if (cs->type != (f->symbol->width ? EXPR_C_INTEGER : EXPR_C_STRING)) {
+        expr_error(ctx, "%s field %s is not compatible with %s constant.",
+                   f->symbol->width ? "Integer" : "String",
+                   f->symbol->name,
+                   cs->type == EXPR_C_INTEGER ? "integer" : "string");
+        return false;
+    }
+
+    if (f->symbol->width) {
+        for (size_t i = 0; i < cs->n_values; i++) {
+            int w = expr_constant_width(&cs->values[i]);
+            if (w > f->symbol->width) {
+                expr_error(ctx, "%d-bit constant is not compatible with "
+                           "%d-bit field %s.",
+                           w, f->symbol->width, f->symbol->name);
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 static struct expr *
 make_cmp(struct expr_context *ctx,
          const struct expr_field *f, enum expr_relop r,
@@ -569,11 +597,7 @@ make_cmp(struct expr_context *ctx,
 {
     struct expr *e = NULL;
 
-    if (cs->type != (f->symbol->width ? EXPR_C_INTEGER : EXPR_C_STRING)) {
-        expr_error(ctx, "Can't compare %s field %s to %s constant.",
-                   f->symbol->width ? "integer" : "string",
-                   f->symbol->name,
-                   cs->type == EXPR_C_INTEGER ? "integer" : "string");
+    if (!type_check(ctx, f, cs)) {
         goto exit;
     }
 
@@ -624,18 +648,6 @@ make_cmp(struct expr_context *ctx,
         }
     }
 
-    if (f->symbol->width) {
-        for (size_t i = 0; i < cs->n_values; i++) {
-            int w = expr_constant_width(&cs->values[i]);
-            if (w > f->symbol->width) {
-                expr_error(ctx, "Cannot compare %d-bit constant against "
-                           "%d-bit field %s.",
-                           w, f->symbol->width, f->symbol->name);
-                goto exit;
-            }
-        }
-    }
-
     e = make_cmp__(f, r, &cs->values[0]);
     for (size_t i = 1; i < cs->n_values; i++) {
         e = expr_combine(r == EXPR_R_EQ ? EXPR_T_OR : EXPR_T_AND,
@@ -2242,6 +2254,10 @@ add_conjunction(const struct expr *and, const struct simap *ports,
                 }
             }
         }
+
+        /* Add the flow that matches on conj_id. */
+        match_set_conj_id(&match, *n_conjsp);
+        expr_match_add(matches, expr_match_new(&match, 0, 0, 0));
     }
 }
 
@@ -2447,3 +2463,118 @@ expr_is_normalized(const struct expr *expr)
         OVS_NOT_REACHED();
     }
 }
+
+/* Action parsing helper. */
+
+static struct expr *
+parse_assignment(struct expr_context *ctx, const struct simap *ports,
+                 struct ofpbuf *ofpacts)
+{
+    struct expr *prereqs = NULL;
+
+    struct expr_field f;
+    if (!parse_field(ctx, &f)) {
+        goto exit;
+    }
+    if (!lexer_match(ctx->lexer, LEX_T_EQUALS)) {
+        expr_syntax_error(ctx, "expecting `='.");
+        goto exit;
+    }
+
+    if (f.symbol->expansion && f.symbol->level != EXPR_L_ORDINAL) {
+        expr_error(ctx, "Can't assign to predicate symbol %s.",
+                   f.symbol->name);
+        goto exit;
+    }
+
+    struct expr_constant_set cs;
+    if (!parse_constant_set(ctx, &cs)) {
+        goto exit;
+    }
+
+    if (!type_check(ctx, &f, &cs)) {
+        goto exit_destroy_cs;
+    }
+    if (cs.in_curlies) {
+        expr_error(ctx, "Assignments require a single value.");
+        expr_constant_set_destroy(&cs);
+        goto exit_destroy_cs;
+    }
+
+    union expr_constant *c = cs.values;
+    for (;;) {
+        /* Accumulate prerequisites. */
+        if (f.symbol->prereqs) {
+            struct ovs_list nesting = OVS_LIST_INITIALIZER(&nesting);
+            char *error;
+            struct expr *e;
+            e = parse_and_annotate(f.symbol->prereqs, ctx->symtab, &nesting,
+                                   &error);
+            if (error) {
+                expr_error(ctx, "%s", error);
+                free(error);
+                goto exit_destroy_cs;
+            }
+            prereqs = expr_combine(EXPR_T_AND, prereqs, e);
+        }
+
+        /* If there's no expansion, we're done. */
+        if (!f.symbol->expansion) {
+            break;
+        }
+
+        /* Expand. */
+        struct expr_field expansion;
+        char *error;
+        if (!parse_field_from_string(f.symbol->expansion, ctx->symtab,
+                                     &expansion, &error)) {
+            expr_error(ctx, "%s", error);
+            free(error);
+            goto exit_destroy_cs;
+        }
+        f.symbol = expansion.symbol;
+        f.ofs += expansion.ofs;
+        mf_subvalue_shift(&c->value, expansion.ofs);
+        mf_subvalue_shift(&c->mask, expansion.ofs);
+    }
+
+    struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+    sf->field = f.symbol->field;
+    if (f.symbol->width) {
+        memcpy(&sf->value, &c->value.u8[sizeof c->value - sf->field->n_bytes],
+               sf->field->n_bytes);
+        memcpy(&sf->mask, &c->mask.u8[sizeof c->mask - sf->field->n_bytes],
+               sf->field->n_bytes);
+    } else {
+        uint32_t port = simap_get(ports, c->string);
+        bitwise_put(port, &sf->value,
+                    sf->field->n_bytes, 0, sf->field->n_bits);
+        bitwise_put(UINT64_MAX, &sf->mask,
+                    sf->field->n_bytes, 0, sf->field->n_bits);
+    }
+
+exit_destroy_cs:
+    expr_constant_set_destroy(&cs);
+exit:
+    return prereqs;
+}
+
+char *
+expr_parse_assignment(struct lexer *lexer, const struct shash *symtab,
+                      const struct simap *ports,
+                      struct ofpbuf *ofpacts, struct expr **prereqsp)
+{
+    struct expr_context ctx;
+    ctx.lexer = lexer;
+    ctx.symtab = symtab;
+    ctx.error = NULL;
+    ctx.not = false;
+
+    struct expr *prereqs = parse_assignment(&ctx, ports, ofpacts);
+    if (ctx.error) {
+        expr_destroy(prereqs);
+        prereqs = NULL;
+    }
+    *prereqsp = prereqs;
+    return ctx.error;
+}
diff --git a/ovn/lib/expr.h b/ovn/lib/expr.h
index 54cec46..7fbbe8f 100644
--- a/ovn/lib/expr.h
+++ b/ovn/lib/expr.h
@@ -60,6 +60,7 @@
 #include "meta-flow.h"
 
 struct ds;
+struct ofpbuf;
 struct shash;
 struct simap;
 
@@ -341,21 +342,24 @@ expr_from_node(const struct ovs_list *node)
 
 void expr_format(const struct expr *, struct ds *);
 void expr_print(const struct expr *);
-struct expr *expr_parse(struct lexer *, const struct shash *, char **errorp);
-struct expr *expr_parse_string(const char *, const struct shash *,
+struct expr *expr_parse(struct lexer *, const struct shash *symtab,
+                        char **errorp);
+struct expr *expr_parse_string(const char *, const struct shash *symtab,
                                char **errorp);
 
 struct expr *expr_clone(struct expr *);
 void expr_destroy(struct expr *);
 
-struct expr *expr_annotate(struct expr *, const struct shash *, char **errorp);
+struct expr *expr_annotate(struct expr *, const struct shash *symtab,
+                           char **errorp);
 struct expr *expr_simplify(struct expr *);
 struct expr *expr_normalize(struct expr *);
 
 bool expr_honors_invariants(const struct expr *);
 bool expr_is_simplified(const struct expr *);
 bool expr_is_normalized(const struct expr *);
-
+
+/* Converting expressions to OpenFlow flows. */
 struct expr_match {
     struct hmap_node hmap_node;
     struct match match;
@@ -367,5 +371,11 @@ uint32_t expr_to_matches(const struct expr *, const struct simap *ports,
                          struct hmap *matches);
 void expr_matches_destroy(struct hmap *matches);
 void expr_matches_print(const struct hmap *matches, FILE *);
+
+/* Action parsing helper. */
+
+char *expr_parse_assignment(struct lexer *lexer, const struct shash *symtab,
+                            const struct simap *ports, struct ofpbuf *ofpacts,
+                            struct expr **prereqsp);
 
 #endif /* ovn/expr.h */
diff --git a/ovn/lib/lex.c b/ovn/lib/lex.c
index 73f0ca3..471abce 100644
--- a/ovn/lib/lex.c
+++ b/ovn/lib/lex.c
@@ -704,6 +704,21 @@ lexer_get(struct lexer *lexer)
     return lexer->token.type;
 }
 
+/* Returns the type of the next token that will be fetched by lexer_get(),
+ * without advancing 'lexer->token' to that token. */
+enum lex_type
+lexer_lookahead(const struct lexer *lexer)
+{
+    struct lex_token next;
+    enum lex_type type;
+    const char *start;
+
+    lex_token_parse(&next, lexer->input, &start);
+    type = next.type;
+    lex_token_destroy(&next);
+    return type;
+}
+
 /* If 'lexer''s current token has the given 'type', advances 'lexer' to the
  * next token and returns true.  Otherwise returns false. */
 bool
@@ -716,3 +731,14 @@ lexer_match(struct lexer *lexer, enum lex_type type)
         return false;
     }
 }
+
+bool
+lexer_match_id(struct lexer *lexer, const char *id)
+{
+    if (lexer->token.type == LEX_T_ID && !strcmp(lexer->token.s, id)) {
+        lexer_get(lexer);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovn/lib/lex.h b/ovn/lib/lex.h
index 29e922c..df4db2d 100644
--- a/ovn/lib/lex.h
+++ b/ovn/lib/lex.h
@@ -105,6 +105,8 @@ void lexer_init(struct lexer *, const char *input);
 void lexer_destroy(struct lexer *);
 
 enum lex_type lexer_get(struct lexer *);
+enum lex_type lexer_lookahead(const struct lexer *);
 bool lexer_match(struct lexer *, enum lex_type);
+bool lexer_match_id(struct lexer *, const char *id);
 
 #endif /* ovn/lex.h */
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index be6430d..3778a95 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -416,6 +416,26 @@ tags_equal(const struct sbrec_bindings *binding,
     return binding->n_tag ? (binding->tag[0] == lport->tag[0]) : true;
 }
 
+struct binding_hash_node {
+    struct hmap_node lp_node; /* In 'lp_map', by binding->logical_port. */
+    struct hmap_node tk_node; /* In 'tk_map', by binding->tunnel_key. */
+    const struct sbrec_bindings *binding;
+};
+
+static bool
+tunnel_key_in_use(const struct hmap *tk_hmap, uint16_t tunnel_key)
+{
+    const struct binding_hash_node *hash_node;
+
+    HMAP_FOR_EACH_IN_BUCKET (hash_node, tk_node, hash_int(tunnel_key, 0),
+                             tk_hmap) {
+        if (hash_node->binding->tunnel_key == tunnel_key) {
+            return true;
+        }
+    }
+    return false;
+}
+
 /*
  * When a change has occurred in the OVN_Northbound database, we go through and
  * make sure that the contents of the Bindings table in the OVN_Southbound
@@ -425,51 +445,59 @@ tags_equal(const struct sbrec_bindings *binding,
 static void
 set_bindings(struct northd_context *ctx)
 {
-    struct hmap bindings_hmap;
     const struct sbrec_bindings *binding;
     const struct nbrec_logical_port *lport;
 
-    struct binding_hash_node {
-        struct hmap_node node;
-        const struct sbrec_bindings *binding;
-    } *hash_node, *hash_node_next;
-
     /*
      * We will need to look up a binding for every logical port.  We don't want
      * to have to do an O(n) search for every binding, so start out by hashing
      * them on the logical port.
      *
      * As we go through every logical port, we will update the binding if it
-     * exists or create one otherwise.  When the update is done, we'll remove it
-     * from the hashmap.  At the end, any bindings left in the hashmap are for
-     * logical ports that have been deleted.
+     * exists or create one otherwise.  When the update is done, we'll remove
+     * it from the hashmap.  At the end, any bindings left in the hashmap are
+     * for logical ports that have been deleted.
+     *
+     * We index the logical_port column because that's the shared key between
+     * the OVN_NB and OVN_SB databases.  We index the tunnel_key column to
+     * allow us to choose a unique tunnel key for any Binding rows we have to
+     * add.
      */
-    hmap_init(&bindings_hmap);
+    struct hmap lp_hmap = HMAP_INITIALIZER(&lp_hmap);
+    struct hmap tk_hmap = HMAP_INITIALIZER(&tk_hmap);
 
     SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) {
-        hash_node = xzalloc(sizeof *hash_node);
+        struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node);
         hash_node->binding = binding;
-        hmap_insert(&bindings_hmap, &hash_node->node,
-                hash_string(binding->logical_port, 0));
+        hmap_insert(&lp_hmap, &hash_node->lp_node,
+                    hash_string(binding->logical_port, 0));
+        hmap_insert(&tk_hmap, &hash_node->tk_node,
+                    hash_int(binding->tunnel_key, 0));
     }
 
     NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) {
+        struct binding_hash_node *hash_node;
         binding = NULL;
-        HMAP_FOR_EACH_WITH_HASH(hash_node, node,
-                hash_string(lport->name, 0), &bindings_hmap) {
+        HMAP_FOR_EACH_WITH_HASH(hash_node, lp_node,
+                                hash_string(lport->name, 0), &lp_hmap) {
             if (!strcmp(lport->name, hash_node->binding->logical_port)) {
                 binding = hash_node->binding;
                 break;
             }
         }
 
+        struct uuid logical_datapath;
+        if (lport->lswitch) {
+            logical_datapath = lport->lswitch->header_.uuid;
+        } else {
+            uuid_zero(&logical_datapath);
+        }
+
         if (binding) {
             /* We found an existing binding for this logical port.  Update its
              * contents. */
 
-            hmap_remove(&bindings_hmap, &hash_node->node);
-            free(hash_node);
-            hash_node = NULL;
+            hmap_remove(&lp_hmap, &hash_node->lp_node);
 
             if (!macs_equal(binding->mac, binding->n_mac,
                         lport->macs, lport->n_macs)) {
@@ -482,6 +510,10 @@ set_bindings(struct northd_context *ctx)
             if (!tags_equal(binding, lport)) {
                 sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
             }
+            if (!uuid_equals(&binding->logical_datapath, &logical_datapath)) {
+                sbrec_bindings_set_logical_datapath(binding,
+                                                    logical_datapath);
+            }
         } else {
             /* There is no binding for this logical port, so create one. */
 
@@ -493,15 +525,31 @@ set_bindings(struct northd_context *ctx)
                 sbrec_bindings_set_parent_port(binding, lport->parent_name);
                 sbrec_bindings_set_tag(binding, lport->tag, lport->n_tag);
             }
+
+            /* Choose unique tunnel_key for the logical port. */
+            static uint16_t next_tunnel_key = 1;
+            while (tunnel_key_in_use(&tk_hmap, next_tunnel_key)) {
+                next_tunnel_key++;
+            }
+            sbrec_bindings_set_tunnel_key(binding, next_tunnel_key++);
+
+            sbrec_bindings_set_logical_datapath(binding, logical_datapath);
         }
     }
 
-    HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &bindings_hmap) {
-        hmap_remove(&bindings_hmap, &hash_node->node);
+    struct binding_hash_node *hash_node;
+    HMAP_FOR_EACH (hash_node, lp_node, &lp_hmap) {
+        hmap_remove(&lp_hmap, &hash_node->lp_node);
         sbrec_bindings_delete(hash_node->binding);
+    }
+    hmap_destroy(&lp_hmap);
+
+    struct binding_hash_node *hash_node_next;
+    HMAP_FOR_EACH_SAFE (hash_node, hash_node_next, tk_node, &tk_hmap) {
+        hmap_remove(&tk_hmap, &hash_node->tk_node);
         free(hash_node);
     }
-    hmap_destroy(&bindings_hmap);
+    hmap_destroy(&tk_hmap);
 }
 
 static void
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index 5f2d1a4..db56211 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -36,7 +36,7 @@
                 "logical_datapath": {"type": "uuid"},
                 "table_id": {"type": {"key": {"type": "integer",
                                               "minInteger": 0,
-                                              "maxInteger": 127}}},
+                                              "maxInteger": 31}}},
                 "priority": {"type": {"key": {"type": "integer",
                                               "minInteger": 0,
                                               "maxInteger": 65535}}},
@@ -47,6 +47,10 @@
             "columns": {
                 "logical_datapath": {"type": "uuid"},
                 "logical_port": {"type": "string"},
+                "tunnel_key": {
+                     "type": {"key": {"type": "integer",
+                                      "minInteger": 1,
+                                      "maxInteger": 65535}}},
                 "parent_port": {"type": {"key": "string", "min": 0, "max": 1}},
                 "tag": {
                      "type": {"key": {"type": "integer",
@@ -57,6 +61,6 @@
                 "mac": {"type": {"key": "string",
                                  "min": 0,
                                  "max": "unlimited"}}},
-            "indexes": [["logical_port"]],
+            "indexes": [["logical_port"], ["tunnel_key"]],
             "isRoot": true}},
     "version": "1.0.0"}
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index bc648e1..705a5f0 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -586,7 +586,11 @@
         <dt><code><var>field</var> = <var>constant</var>;</code></dt>
         <dd>
 	  Sets data or metadata field <var>field</var> to constant value
-	  <var>constant</var>.
+	  <var>constant</var>.  Assigning to a field with prerequisites
+	  implicitly adds those prerequisites to <ref column="match"/>; thus,
+	  for example, a flow that sets <code>tcp.dst</code> applies only to
+	  TCP flows, regardless of whether its <ref column="match"/> mentions
+	  any TCP field.
 	</dd>
       </dl>
 
@@ -665,6 +669,10 @@
       prescribe a particular format for the logical port ID.
     </column>
 
+    <column name="tunnel_key">
+      A number that represents the logical port in tunnel IDs.
+    </column>
+
     <column name="parent_port">
       For containers created inside a VM, this is taken from
       <ref table="Logical_Port" column="parent_name" db="OVN_Northbound"/>
-- 
2.1.3




More information about the dev mailing list