[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