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 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev