ovn: New module for parsing OVN actions as OpenFlow.
authorBen Pfaff <blp@nicira.com>
Thu, 30 Apr 2015 06:48:55 +0000 (23:48 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 4 May 2015 01:52:24 +0000 (18:52 -0700)
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Justin Pettit <jpettit@nicira.com>
ovn/lib/actions.c [new file with mode: 0644]
ovn/lib/actions.h [new file with mode: 0644]
ovn/lib/automake.mk
ovn/lib/expr.c
ovn/lib/expr.h
ovn/ovn-sb.ovsschema
ovn/ovn-sb.xml
tests/ovn.at
tests/test-ovn.c

diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
new file mode 100644 (file)
index 0000000..28be688
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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 next_table_id;      /* OpenFlow table for 'next' to resubmit. */
+    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, "next")) {
+            if (ctx->next_table_id) {
+                emit_resubmit(ctx, ctx->next_table_id);
+            } else {
+                action_error(ctx, "\"next\" action not allowed here.");
+            }
+        } else if (lexer_match_id(ctx->lexer, "output")) {
+            /* Table 64 does logical-to-physical translation. */
+            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;
+        }
+    }
+}
+
+/* Parses OVN actions, in the format described for the "actions" column in the
+ * Pipeline table in ovn-sb(5), and appends the parsed versions of the actions
+ * to 'ofpacts' as "struct ofpact"s.
+ *
+ * 'symtab' provides a table of "struct expr_symbol"s to support (as one would
+ * provide to expr_parse()).
+ *
+ * 'ports' must be a map from strings (presumably names of ports) to integers
+ * (as one would provide to expr_to_matches()).  Strings used in the actions
+ * that are not in 'ports' are translated to zero.
+ *
+ * 'next_table_id' should be the OpenFlow table to which the "next" action will
+ * resubmit, or 0 to disable "next".
+ *
+ * Some actions add extra requirements (prerequisites) to the flow's match.  If
+ * so, this function sets '*prereqsp' to the actions' prerequisites; otherwise,
+ * it sets '*prereqsp' to NULL.  The caller owns '*prereqsp' and must
+ * eventually free it.
+ *
+ * Returns NULL on success, otherwise a malloc()'d error message that the
+ * caller must free.  On failure, 'ofpacts' has the same contents and
+ * '*prereqsp' is set to NULL, but some tokens may have been consumed from
+ * 'lexer'.
+  */
+char * OVS_WARN_UNUSED_RESULT
+actions_parse(struct lexer *lexer, const struct shash *symtab,
+              const struct simap *ports, uint8_t next_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.next_table_id = next_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 * OVS_WARN_UNUSED_RESULT
+actions_parse_string(const char *s, const struct shash *symtab,
+                     const struct simap *ports, uint8_t next_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, next_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 (file)
index 0000000..0139bfc
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+#include <stdint.h>
+#include "compiler.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 next_table_id,
+                    struct ofpbuf *ofpacts, struct expr **prereqsp)
+    OVS_WARN_UNUSED_RESULT;;
+char *actions_parse_string(const char *s, const struct shash *symtab,
+                           const struct simap *ports, uint8_t next_table_id,
+                           struct ofpbuf *ofpacts, struct expr **prereqsp)
+    OVS_WARN_UNUSED_RESULT;;
+
+#endif /* ovn/actions.h */
index 454f2ef..956c83f 100644 (file)
@@ -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 \
index a1275a3..c81c453 100644 (file)
@@ -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,
@@ -1334,7 +1346,9 @@ parse_and_annotate(const char *s, const struct shash *symtab,
     if (expr) {
         expr = expr_annotate__(expr, symtab, nesting, &error);
     }
-    if (!expr) {
+    if (expr) {
+        *errorp = NULL;
+    } else {
         *errorp = xasprintf("Error parsing expression `%s' encountered as "
                             "prerequisite or predicate of initial expression: "
                             "%s", s, error);
@@ -2465,3 +2479,132 @@ expr_is_normalized(const struct expr *expr)
         OVS_NOT_REACHED();
     }
 }
+\f
+/* 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.");
+        goto exit_destroy_cs;
+    }
+
+    const struct expr_symbol *orig_symbol = f.symbol;
+    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;
+    }
+
+    if (!f.symbol->field->writable) {
+        expr_error(ctx, "Field %s is not modifiable.", orig_symbol->name);
+        goto exit_destroy_cs;
+    }
+
+    struct ofpact_set_field *sf = ofpact_put_SET_FIELD(ofpacts);
+    sf->field = f.symbol->field;
+    if (f.symbol->width) {
+        mf_subvalue_shift(&c->value, f.ofs);
+        if (!c->masked) {
+            memset(&c->mask, 0, sizeof c->mask);
+            bitwise_one(&c->mask, sizeof c->mask, f.ofs, f.n_bits);
+        } else {
+            mf_subvalue_shift(&c->mask, f.ofs);
+        }
+
+        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;
+}
+
+/* A helper for actions_parse(), to parse an OVN assignment action in the form
+ * "field = value" into 'ofpacts'.  The parameters and return value match those
+ * for actions_parse(). */
+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;
+}
index 3540708..501152e 100644 (file)
@@ -60,6 +60,7 @@
 #include "meta-flow.h"
 
 struct ds;
+struct ofpbuf;
 struct shash;
 struct simap;
 
@@ -341,14 +342,16 @@ 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 *);
 
@@ -371,5 +374,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 *);
+\f
+/* 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 */
index 363e664..a29e986 100644 (file)
@@ -38,7 +38,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}}},
index 2afc9a1..bc3a20e 100644 (file)
         <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>, e.g. <code>outport = "vif0";</code> to set the
+         logical output port.  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.  To set only a subset of bits in a field,
+         <var>field</var> may be a subfield or <var>constant</var> may be
+         masked, e.g. <code>vlan.pcp[2] = 1;</code> and <code>vlan.pcp =
+         4/4;</code> both set the most sigificant bit of the VLAN PCP.  Not
+         all fields are modifiable (e.g. <code>eth.type</code> and
+         <code>ip.proto</code> are read-only), and not all modifiable fields
+         may be partially modified (e.g. <code>ip.ttl</code> must assigned as
+         a whole).
        </dd>
       </dl>
 
index 24479ec..ed79192 100644 (file)
@@ -200,8 +200,8 @@ ip6.src == ::1 => ip6.src == 0x1
 inport == "eth0"
 !(inport != "eth0") => inport == "eth0"
 
-ip4.src == "eth0" => Can't compare integer field ip4.src to string constant.
-inport == 1 => Can't compare string field inport to integer constant.
+ip4.src == "eth0" => Integer field ip4.src is not compatible with string constant.
+inport == 1 => String field inport is not compatible with integer constant.
 
 ip4.src > {1, 2, 3} => Only == and != operators may be used with value sets.
 eth.type > 0x800 => Only == and != operators may be used with nominal field eth.type.
@@ -253,7 +253,7 @@ eth.dst == {} => Syntax error at `}' expecting constant.
 
 eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff => Only == and != operators may be used with masked constants.  Consider using subfields instead (e.g. eth.src[0..15] > 0x1111 in place of eth.src > 00:00:00:00:11:11/00:00:00:00:ff:ff).
 
-ip4.src == ::1 => Cannot compare 128-bit constant against 32-bit field ip4.src.
+ip4.src == ::1 => 128-bit constant is not compatible with 32-bit field ip4.src.
 
 1 == eth.type == 2 => Range expressions must have the form `x < field < y' or `x > field > y', with each `<' optionally replaced by `<=' or `>' by `>=').
 ]])
@@ -351,31 +351,74 @@ AT_SETUP([ovn -- converting expressions to flows -- string fields])
 expr_to_flow () {
     echo "$1" | ovstest test-ovn expr-to-flows | sort
 }
-AT_CHECK([expr_to_flow 'inport == "eth0"'], [0], [in_port=5
+AT_CHECK([expr_to_flow 'inport == "eth0"'], [0], [reg6=0x5
 ])
-AT_CHECK([expr_to_flow 'inport == "eth1"'], [0], [in_port=6
+AT_CHECK([expr_to_flow 'inport == "eth1"'], [0], [reg6=0x6
 ])
 AT_CHECK([expr_to_flow 'inport == "eth2"'], [0], [(no flows)
 ])
 AT_CHECK([expr_to_flow 'inport == "eth0" && ip'], [0], [dnl
-ip,in_port=5
-ipv6,in_port=5
+ip,reg6=0x5
+ipv6,reg6=0x5
 ])
 AT_CHECK([expr_to_flow 'inport == "eth1" && ip'], [0], [dnl
-ip,in_port=6
-ipv6,in_port=6
+ip,reg6=0x6
+ipv6,reg6=0x6
 ])
 AT_CHECK([expr_to_flow 'inport == "eth2" && ip'], [0], [(no flows)
 ])
 AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2", "LOCAL"}'], [0],
-[in_port=5
-in_port=6
-in_port=LOCAL
+[reg6=0x5
+reg6=0x6
+reg6=0xfffe
 ])
 AT_CHECK([expr_to_flow 'inport == {"eth0", "eth1", "eth2"} && ip'], [0], [dnl
-ip,in_port=5
-ip,in_port=6
-ipv6,in_port=5
-ipv6,in_port=6
+ip,reg6=0x5
+ip,reg6=0x6
+ipv6,reg6=0x5
+ipv6,reg6=0x6
 ])
 AT_CLEANUP
+
+AT_SETUP([ovn -- action parsing])
+dnl Text before => is input, text after => is expected output.
+AT_DATA([test-cases.txt], [[
+# Positive tests.
+drop; => actions=drop, prereqs=1
+next; => actions=resubmit(,11), prereqs=1
+output; => actions=resubmit(,64), prereqs=1
+outport="eth0"; next; outport="LOCAL"; next; => actions=set_field:0x5->reg7,resubmit(,11),set_field:0xfffe->reg7,resubmit(,11), prereqs=1
+tcp.dst=80; => actions=set_field:80->tcp_dst, prereqs=ip.proto == 0x6 && (eth.type == 0x800 || eth.type == 0x86dd)
+eth.dst[40] = 1; => actions=set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_dst, prereqs=1
+vlan.pcp = 2; => actions=set_field:0x4000/0xe000->vlan_tci, prereqs=vlan.tci[12]
+vlan.tci[13..15] = 2; => actions=set_field:0x4000/0xe000->vlan_tci, prereqs=1
+
+## Negative tests.
+
+; => Syntax error at `;'.
+xyzzy; => Syntax error at `xyzzy' expecting action.
+next; 123; => Syntax error at `123'.
+next; xyzzy; => Syntax error at `xyzzy' expecting action.
+
+# "drop;" must be on its own:
+drop; next; => Syntax error at `next' expecting end of input.
+next; drop; => Syntax error at `drop' expecting action.
+
+# Missing ";":
+next => Syntax error at end of input expecting ';'.
+
+inport[1] = 1; => Cannot select subfield of string field inport.
+ip.proto[1] = 1; => Cannot select subfield of nominal field ip.proto.
+eth.dst[40] == 1; => Syntax error at `==' expecting `='.
+ip = 1; => Can't assign to predicate symbol ip.
+ip.proto = 6; => Field ip.proto is not modifiable.
+inport = {"a", "b"}; => Assignments require a single value.
+inport = {}; => Syntax error at `}' expecting constant.
+bad_prereq = 123; => Error parsing expression `xyzzy' encountered as prerequisite or predicate of initial expression: Syntax error at `xyzzy' expecting field name.
+self_recurse = 123; => Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Error parsing expression `self_recurse != 0' encountered as prerequisite or predicate of initial expression: Recursive expansion of symbol `self_recurse'.
+vlan.present = 0; => Can't assign to predicate symbol vlan.present.
+]])
+sed 's/ =>.*//' test-cases.txt > input.txt
+sed 's/.* => //' test-cases.txt > expout
+AT_CHECK([ovstest test-ovn parse-actions < input.txt], [0], [expout])
+AT_CLEANUP
index b6fdb0c..1a005b8 100644 (file)
@@ -22,6 +22,9 @@
 #include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "match.h"
+#include "ofp-actions.h"
+#include "ofpbuf.h"
+#include "ovn/lib/actions.h"
 #include "ovn/lib/expr.h"
 #include "ovn/lib/lex.h"
 #include "ovs-thread.h"
@@ -132,13 +135,15 @@ create_symtab(struct shash *symtab)
 {
     shash_init(symtab);
 
-    expr_symtab_add_string(symtab, "inport", MFF_IN_PORT, NULL);
-    expr_symtab_add_string(symtab, "outport", MFF_ACTSET_OUTPUT, NULL);
+    /* 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);
 
     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_field(symtab, "xreg3", MFF_XREG3, NULL, false);
 
     expr_symtab_add_subfield(symtab, "reg0", NULL, "xreg0[32..63]");
     expr_symtab_add_subfield(symtab, "reg1", NULL, "xreg0[0..31]");
@@ -146,8 +151,6 @@ create_symtab(struct shash *symtab)
     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]");
-    expr_symtab_add_subfield(symtab, "reg6", NULL, "xreg3[32..63]");
-    expr_symtab_add_subfield(symtab, "reg7", NULL, "xreg3[0..31]");
 
     expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
     expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
@@ -1110,6 +1113,60 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
     shash_destroy(&symtab);
 }
 \f
+/* Actions. */
+
+static void
+test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+    struct shash symtab;
+    struct simap ports;
+    struct ds input;
+
+    create_symtab(&symtab);
+
+    simap_init(&ports);
+    simap_put(&ports, "eth0", 5);
+    simap_put(&ports, "eth1", 6);
+    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
+
+    ds_init(&input);
+    while (!ds_get_test_line(&input, stdin)) {
+        struct ofpbuf ofpacts;
+        struct expr *prereqs;
+        char *error;
+
+        ofpbuf_init(&ofpacts, 0);
+        error = actions_parse_string(ds_cstr(&input), &symtab, &ports, 11,
+                                     &ofpacts, &prereqs);
+        if (!error) {
+            struct ds output;
+
+            ds_init(&output);
+            ds_put_cstr(&output, "actions=");
+            ofpacts_format(ofpacts.data, ofpacts.size, &output);
+            ds_put_cstr(&output, ", prereqs=");
+            if (prereqs) {
+                expr_format(prereqs, &output);
+            } else {
+                ds_put_char(&output, '1');
+            }
+            puts(ds_cstr(&output));
+            ds_destroy(&output);
+        } else {
+            puts(error);
+            free(error);
+        }
+
+        expr_destroy(prereqs);
+        ofpbuf_uninit(&ofpacts);
+    }
+    ds_destroy(&input);
+
+    simap_destroy(&ports);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+}
+\f
 static unsigned int
 parse_relops(const char *s)
 {
@@ -1266,7 +1323,10 @@ test_ovn_main(int argc, char *argv[])
     }
 
     static const struct ovs_cmdl_command commands[] = {
+        /* Lexer. */
         {"lex", NULL, 0, 0, test_lex},
+
+        /* Expressions. */
         {"parse-expr", NULL, 0, 0, test_parse_expr},
         {"annotate-expr", NULL, 0, 0, test_annotate_expr},
         {"simplify-expr", NULL, 0, 0, test_simplify_expr},
@@ -1276,6 +1336,10 @@ test_ovn_main(int argc, char *argv[])
         {"composition", NULL, 1, 1, test_composition},
         {"tree-shape", NULL, 1, 1, test_tree_shape},
         {"exhaustive", NULL, 1, 1, test_exhaustive},
+
+        /* Actions. */
+        {"parse-actions", NULL, 0, 0, test_parse_actions},
+
         {NULL, NULL, 0, 0, NULL},
     };
     struct ovs_cmdl_context ctx;