diff mbox

[ovs-dev,v4] ovn: expose c validator to python

Message ID 1469484449-6505-1-git-send-email-aaronorosen@gmail.com
State Deferred
Delegated to: Russell Bryant
Headers show

Commit Message

YourName July 25, 2016, 10:07 p.m. UTC
This patch exposes the c function expr_parse_string() to be called via
python. The motivation for this is so that clients interfacing with
ovn can call this method in order to validate the data they are writting
to ovn.

Previously, there were several bugs in the neutron/ovn integration
that went unnoticed due to the client writing invalid data. This should
hopefully help catch errors like this earlier as it can now be detected on
the client side and an error can be raised.

Signed-off-by: Aaron Rosen <aaronorosen@gmail.com>
---
 include/ovn/expr.h      |   1 +
 ovn/lib/expr.c          | 107 +++++++++++++++++++++++++++++++++++++++++++++
 python/automake.mk      |   4 +-
 python/ovs/_ovn-utils.c | 104 ++++++++++++++++++++++++++++++++++++++++++++
 python/setup.py         |  31 +++++++++++++-
 tests/automake.mk       |   4 +-
 tests/test-ovn-utils.at |  15 +++++++
 tests/test-ovn-utils.py |  33 ++++++++++++++
 tests/test-ovn.c        | 112 +-----------------------------------------------
 tests/testsuite.at      |   1 +
 10 files changed, 299 insertions(+), 113 deletions(-)
 create mode 100644 python/ovs/_ovn-utils.c
 create mode 100644 tests/test-ovn-utils.at
 create mode 100644 tests/test-ovn-utils.py

Comments

YourName July 25, 2016, 10:08 p.m. UTC | #1
Note: this patch is rebased on top of : [PATCH] move
ovn/lib/<lex|actions|expr>.h to include/ovn


On Mon, Jul 25, 2016 at 3:07 PM, Aaron Rosen <aaronorosen@gmail.com> wrote:

> This patch exposes the c function expr_parse_string() to be called via
> python. The motivation for this is so that clients interfacing with
> ovn can call this method in order to validate the data they are writting
> to ovn.
>
> Previously, there were several bugs in the neutron/ovn integration
> that went unnoticed due to the client writing invalid data. This should
> hopefully help catch errors like this earlier as it can now be detected on
> the client side and an error can be raised.
>
> Signed-off-by: Aaron Rosen <aaronorosen@gmail.com>
> ---
>  include/ovn/expr.h      |   1 +
>  ovn/lib/expr.c          | 107
> +++++++++++++++++++++++++++++++++++++++++++++
>  python/automake.mk      |   4 +-
>  python/ovs/_ovn-utils.c | 104 ++++++++++++++++++++++++++++++++++++++++++++
>  python/setup.py         |  31 +++++++++++++-
>  tests/automake.mk       |   4 +-
>  tests/test-ovn-utils.at |  15 +++++++
>  tests/test-ovn-utils.py |  33 ++++++++++++++
>  tests/test-ovn.c        | 112
> +-----------------------------------------------
>  tests/testsuite.at      |   1 +
>  10 files changed, 299 insertions(+), 113 deletions(-)
>  create mode 100644 python/ovs/_ovn-utils.c
>  create mode 100644 tests/test-ovn-utils.at
>  create mode 100644 tests/test-ovn-utils.py
>
> diff --git a/include/ovn/expr.h b/include/ovn/expr.h
> index d790c49..381e32f 100644
> --- a/include/ovn/expr.h
> +++ b/include/ovn/expr.h
> @@ -470,5 +470,6 @@ void expr_macros_add(struct shash *macros, const char
> *name,
>                       const char * const *values, size_t n_values);
>  void expr_macros_remove(struct shash *macros, const char *name);
>  void expr_macros_destroy(struct shash *macros);
> +void create_symtab_helper(struct shash *symtab);
>
>  #endif /* ovn/expr.h */
> diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
> index 288aae2..94b93c5 100644
> --- a/ovn/lib/expr.c
> +++ b/ovn/lib/expr.c
> @@ -3008,3 +3008,110 @@ expr_parse_constant_set(struct lexer *lexer, const
> struct shash *symtab,
>      parse_constant_set(&ctx, cs);
>      return ctx.error;
>  }
> +
> +/* create_symtab_helper() is used for testing purposes and in the
> ovn_utils
> + * python package. */
> +void
> +create_symtab_helper(struct shash *symtab)
> +{
> +    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);
> +
> +    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]");
> +
> +    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);
> +
> +    /* For negative testing. */
> +    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy",
> false);
> +    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
> +                          "self_recurse != 0", false);
> +    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
> +                          "mutual_recurse_2 != 0", false);
> +    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
> +                          "mutual_recurse_1 != 0", false);
> +    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
> +}
> diff --git a/python/automake.mk b/python/automake.mk
> index 874a178..b4f4ef3 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -47,7 +47,9 @@ EXTRA_DIST += \
>         python/setup.py
>
>  # C extension support.
> -EXTRA_DIST += python/ovs/_json.c
> +EXTRA_DIST += \
> +       python/ovs/_json.c \
> +   python/ovs/_ovn-utils.c
>
>  PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles)
>  EXTRA_DIST += $(PYFILES)
> diff --git a/python/ovs/_ovn-utils.c b/python/ovs/_ovn-utils.c
> new file mode 100644
> index 0000000..e2395ec
> --- /dev/null
> +++ b/python/ovs/_ovn-utils.c
> @@ -0,0 +1,104 @@
> +/*
> + * Copyright (c) 2016 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 <Python.h>
> +#include "ovn/actions.h"
> +#include "ovn/expr.h"
> +#include "ovn/lex.h"
> +#include "openvswitch/shash.h"
> +
> +#if PY_MAJOR_VERSION >= 3
> +#define IS_PY3K
> +#endif
> +
> +#ifdef IS_PY3K
> +PyMODINIT_FUNC PyInit_ovn_utils(void);
> +#else
> +void initovn_utils(void);
> +#endif
> +
> +static char parse_match_docs[] =
> +    "Specify match string to validate\n";
> +
> +static PyObject* parse_match(PyObject* self OVS_UNUSED, PyObject *args)
> +{
> +    char *match_string;
> +    PyObject *error_string;
> +
> +    if (!PyArg_ParseTuple(args, "s", &match_string)) {
> +               return Py_BuildValue("s", "Unable to parse input");
> +    }
> +
> +    struct shash symtab;
> +    create_symtab_helper(&symtab);
> +    struct expr *expr;
> +    char *error;
> +    expr = expr_parse_string(match_string, &symtab, NULL, &error);
> +
> +    expr_destroy(expr);
> +    expr_symtab_destroy(&symtab);
> +    shash_destroy(&symtab);
> +    if(error) {
> +#ifdef IS_PY3K
> +      error_string = PyUnicode_FromString(error);
> +#else
> +      error_string = PyString_FromString(error);
> +#endif
> +      free(error);
> +               return error_string;
> +    }
> +    Py_RETURN_NONE;
> +}
> +
> +
> +static PyMethodDef ovn_utils_funcs[] = {
> +    {"parse_match", parse_match, METH_VARARGS, parse_match_docs},
> +    {NULL}
> +};
> +
> +#ifdef IS_PY3K
> +static struct PyModuleDef moduledef = {
> +    PyModuleDef_HEAD_INIT,
> +    "ovs.ovn_utils",            /* m_name */
> +    "OVN helper utilities",     /* m_doc */
> +    0,                          /* m_size */
> +    ovn_utils_funcs,                /* m_methods */
> +    0,                          /* m_slots */
> +    0,                          /* m_traverse */
> +    0,                          /* m_clear */
> +    0,                          /* m_free */
> +};
> +#define INITERROR return NULL
> +#else /* !IS_PY3K */
> +#define INITERROR return
> +#endif
> +
> +PyMODINIT_FUNC
> +#ifdef IS_PY3K
> +PyInit_ovn_utils(void)
> +#else
> +initovn_utils(void)
> +#endif
> +{
> +#ifdef IS_PY3K
> +    PyObject *m;
> +    m = PyModule_Create(&moduledef);
> +    return m;
> +#else
> +    Py_InitModule3("ovs.ovn_utils", ovn_utils_funcs,
> +                   "OVN helper utilities");
> +#endif
> +}
> diff --git a/python/setup.py b/python/setup.py
> index 19c1f18..5269151 100644
> --- a/python/setup.py
> +++ b/python/setup.py
> @@ -77,7 +77,36 @@ setup_args = dict(
>          'Programming Language :: Python :: 3.4',
>      ],
>      ext_modules=[setuptools.Extension("ovs._json",
> sources=["ovs/_json.c"],
> -                                      libraries=['openvswitch'])],
> +                                      libraries=['openvswitch']),
> +                 setuptools.Extension("ovs.ovn_utils",
> +                                      # XXX there must be a better way
> +                                      # to do this?
> +                                      libraries=['openvswitch'],
> +                                      extra_compile_args=([
> +                                          '-Wstrict-prototypes',
> +                                          '-Wall',
> +                                          '-Wextra',
> +                                          '-shared',
> +                                          '-Wno-sign-compare',
> +                                          '-Wpointer-arith',
> +                                          '-Wformat-security',
> +                                          '-Wswitch-enum',
> +                                          '-Wunused-parameter',
> +                                          '-Wbad-function-cast',
> +                                          '-Wcast-align',
> +                                          '-Wmissing-prototypes',
> +                                          '-Wmissing-field-initializers',
> +                                          '-fno-strict-aliasing',
> +                                          '-std=gnu99',
> +                                          '-DHAVE_CONFIG_H']),
> +                                      include_dirs=['../include/',
> +                                                    '../lib/',
> +                                                    '../ovn/lib',
> +                                                    '../'],
> +                                      sources=["ovs/_ovn-utils.c",
> +                                               "../ovn/lib/lex.c",
> +                                               "../ovn/lib/expr.c",
> +                                               "../ovn/lib/actions.c"])],
>      cmdclass={'build_ext': try_build_ext},
>  )
>
> diff --git a/tests/automake.mk b/tests/automake.mk
> index 575ffeb..c45a5b5 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -94,7 +94,8 @@ TESTSUITE_AT = \
>         tests/ovn-nbctl.at \
>         tests/ovn-sbctl.at \
>         tests/ovn-controller.at \
> -       tests/ovn-controller-vtep.at
> +       tests/ovn-controller-vtep.at \
> +       tests/test-ovn-utils.at
>
>  SYSTEM_KMOD_TESTSUITE_AT = \
>         tests/system-common-macros.at \
> @@ -385,6 +386,7 @@ CHECK_PYFILES = \
>         tests/test-jsonrpc.py \
>         tests/test-l7.py \
>         tests/test-ovsdb.py \
> +       tests/test-ovn-utils.py \
>         tests/test-reconnect.py \
>         tests/MockXenAPI.py \
>         tests/test-unix-socket.py \
> diff --git a/tests/test-ovn-utils.at b/tests/test-ovn-utils.at
> new file mode 100644
> index 0000000..66e5c36
> --- /dev/null
> +++ b/tests/test-ovn-utils.at
> @@ -0,0 +1,15 @@
> +AT_BANNER([Test OVN Utils])
> +
> +m4_define([TEST_OVN_UTILS],
> +       [AT_SETUP([test-ovn-utils - $1])
> +    AT_SKIP_IF([test $2 = no])
> +    # Skip test if user doesn't have ovs.ovn_utils module
> +    AT_SKIP_IF([! $3 -c 'import ovs.ovn_utils' 2> /dev/null])
> +    AT_KEYWORDS([ovnutils])
> +    AT_CHECK([$3 $srcdir/test-ovn-utils.py],
> +             [0],[ignore], [ignore])
> +    AT_CLEANUP])
> +
> +
> +TEST_OVN_UTILS([Python2], [$HAVE_PYTHON], [$PYTHON])
> +TEST_OVN_UTILS([Python3], [$HAVE_PYTHON3], [$PYTHON3])
> diff --git a/tests/test-ovn-utils.py b/tests/test-ovn-utils.py
> new file mode 100644
> index 0000000..fecf2c0
> --- /dev/null
> +++ b/tests/test-ovn-utils.py
> @@ -0,0 +1,33 @@
> +# Copyright (c) 2016 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.
> +
> +import unittest
> +
> +from ovs import ovn_utils
> +
> +
> +class TestOvnUtils(unittest.TestCase):
> +
> +    def test_parse_match_fail(self):
> +        expected = "Syntax error at `a' expecting field name."
> +        result = ovn_utils.parse_match("a")
> +        self.assertEqual(result, expected)
> +
> +    def test_parse_match_success(self):
> +        result = ovn_utils.parse_match(
> +            'outport == "25560992-3f36-4a8b-bc19-e3f84188ef33" && ip4 &&
> udp')
> +        self.assertEqual(result, None)
> +
> +if __name__ == '__main__':
> +    unittest.main()
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index ffb4e50..ba3fcc6 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -136,114 +136,6 @@ test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
>  }
>
>  static void
> -create_symtab(struct shash *symtab)
> -{
> -    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_REG14, NULL);
> -    expr_symtab_add_string(symtab, "outport", MFF_REG15, NULL);
> -
> -    expr_symtab_add_field(symtab, "xxreg0", MFF_XXREG0, NULL, false);
> -    expr_symtab_add_field(symtab, "xxreg1", MFF_XXREG1, NULL, false);
> -
> -    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]");
> -
> -    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);
> -
> -    /* For negative testing. */
> -    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy",
> false);
> -    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
> -                          "self_recurse != 0", false);
> -    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
> -                          "mutual_recurse_2 != 0", false);
> -    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
> -                          "mutual_recurse_1 != 0", false);
> -    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
> -}
> -
> -static void
>  create_dhcp_opts(struct hmap *dhcp_opts)
>  {
>      hmap_init(dhcp_opts);
> @@ -311,7 +203,7 @@ test_parse_expr__(int steps)
>      struct simap ports;
>      struct ds input;
>
> -    create_symtab(&symtab);
> +    create_symtab_helper(&symtab);
>      create_macros(&macros);
>
>      simap_init(&ports);
> @@ -1281,7 +1173,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx
> OVS_UNUSED)
>      struct simap ports, ct_zones;
>      struct ds input;
>
> -    create_symtab(&symtab);
> +    create_symtab_helper(&symtab);
>      create_dhcp_opts(&dhcp_opts);
>
>      /* Initialize group ids. */
> diff --git a/tests/testsuite.at b/tests/testsuite.at
> index f5f1253..1721ba8 100644
> --- a/tests/testsuite.at
> +++ b/tests/testsuite.at
> @@ -76,3 +76,4 @@ m4_include([tests/ovn-nbctl.at])
>  m4_include([tests/ovn-sbctl.at])
>  m4_include([tests/ovn-controller.at])
>  m4_include([tests/ovn-controller-vtep.at])
> +m4_include([tests/test-ovn-utils.at])
> --
> 1.9.1
>
>
diff mbox

Patch

diff --git a/include/ovn/expr.h b/include/ovn/expr.h
index d790c49..381e32f 100644
--- a/include/ovn/expr.h
+++ b/include/ovn/expr.h
@@ -470,5 +470,6 @@  void expr_macros_add(struct shash *macros, const char *name,
                      const char * const *values, size_t n_values);
 void expr_macros_remove(struct shash *macros, const char *name);
 void expr_macros_destroy(struct shash *macros);
+void create_symtab_helper(struct shash *symtab);
 
 #endif /* ovn/expr.h */
diff --git a/ovn/lib/expr.c b/ovn/lib/expr.c
index 288aae2..94b93c5 100644
--- a/ovn/lib/expr.c
+++ b/ovn/lib/expr.c
@@ -3008,3 +3008,110 @@  expr_parse_constant_set(struct lexer *lexer, const struct shash *symtab,
     parse_constant_set(&ctx, cs);
     return ctx.error;
 }
+
+/* create_symtab_helper() is used for testing purposes and in the ovn_utils
+ * python package. */
+void
+create_symtab_helper(struct shash *symtab)
+{
+    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);
+
+    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]");
+
+    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);
+
+    /* For negative testing. */
+    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
+    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
+                          "self_recurse != 0", false);
+    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
+                          "mutual_recurse_2 != 0", false);
+    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
+                          "mutual_recurse_1 != 0", false);
+    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
+}
diff --git a/python/automake.mk b/python/automake.mk
index 874a178..b4f4ef3 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -47,7 +47,9 @@  EXTRA_DIST += \
 	python/setup.py
 
 # C extension support.
-EXTRA_DIST += python/ovs/_json.c
+EXTRA_DIST += \
+	python/ovs/_json.c \
+   python/ovs/_ovn-utils.c
 
 PYFILES = $(ovs_pyfiles) python/ovs/dirs.py $(ovstest_pyfiles)
 EXTRA_DIST += $(PYFILES)
diff --git a/python/ovs/_ovn-utils.c b/python/ovs/_ovn-utils.c
new file mode 100644
index 0000000..e2395ec
--- /dev/null
+++ b/python/ovs/_ovn-utils.c
@@ -0,0 +1,104 @@ 
+/*
+ * Copyright (c) 2016 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 <Python.h>
+#include "ovn/actions.h"
+#include "ovn/expr.h"
+#include "ovn/lex.h"
+#include "openvswitch/shash.h"
+
+#if PY_MAJOR_VERSION >= 3
+#define IS_PY3K
+#endif
+
+#ifdef IS_PY3K
+PyMODINIT_FUNC PyInit_ovn_utils(void);
+#else
+void initovn_utils(void);
+#endif
+
+static char parse_match_docs[] =
+    "Specify match string to validate\n";
+
+static PyObject* parse_match(PyObject* self OVS_UNUSED, PyObject *args)
+{
+    char *match_string;
+    PyObject *error_string;
+
+    if (!PyArg_ParseTuple(args, "s", &match_string)) {
+		return Py_BuildValue("s", "Unable to parse input");
+    }
+
+    struct shash symtab;
+    create_symtab_helper(&symtab);
+    struct expr *expr;
+    char *error;
+    expr = expr_parse_string(match_string, &symtab, NULL, &error);
+
+    expr_destroy(expr);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+    if(error) {
+#ifdef IS_PY3K
+      error_string = PyUnicode_FromString(error);
+#else
+      error_string = PyString_FromString(error);
+#endif
+      free(error);
+		return error_string;
+    }
+    Py_RETURN_NONE;
+}
+
+
+static PyMethodDef ovn_utils_funcs[] = {
+    {"parse_match", parse_match, METH_VARARGS, parse_match_docs},
+    {NULL}
+};
+
+#ifdef IS_PY3K
+static struct PyModuleDef moduledef = {
+    PyModuleDef_HEAD_INIT,
+    "ovs.ovn_utils",            /* m_name */
+    "OVN helper utilities",     /* m_doc */
+    0,                          /* m_size */
+    ovn_utils_funcs,                /* m_methods */
+    0,                          /* m_slots */
+    0,                          /* m_traverse */
+    0,                          /* m_clear */
+    0,                          /* m_free */
+};
+#define INITERROR return NULL
+#else /* !IS_PY3K */
+#define INITERROR return
+#endif
+
+PyMODINIT_FUNC
+#ifdef IS_PY3K
+PyInit_ovn_utils(void)
+#else
+initovn_utils(void)
+#endif
+{
+#ifdef IS_PY3K
+    PyObject *m;
+    m = PyModule_Create(&moduledef);
+    return m;
+#else
+    Py_InitModule3("ovs.ovn_utils", ovn_utils_funcs,
+                   "OVN helper utilities");
+#endif
+}
diff --git a/python/setup.py b/python/setup.py
index 19c1f18..5269151 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -77,7 +77,36 @@  setup_args = dict(
         'Programming Language :: Python :: 3.4',
     ],
     ext_modules=[setuptools.Extension("ovs._json", sources=["ovs/_json.c"],
-                                      libraries=['openvswitch'])],
+                                      libraries=['openvswitch']),
+                 setuptools.Extension("ovs.ovn_utils",
+                                      # XXX there must be a better way
+                                      # to do this?
+                                      libraries=['openvswitch'],
+                                      extra_compile_args=([
+                                          '-Wstrict-prototypes',
+                                          '-Wall',
+                                          '-Wextra',
+                                          '-shared',
+                                          '-Wno-sign-compare',
+                                          '-Wpointer-arith',
+                                          '-Wformat-security',
+                                          '-Wswitch-enum',
+                                          '-Wunused-parameter',
+                                          '-Wbad-function-cast',
+                                          '-Wcast-align',
+                                          '-Wmissing-prototypes',
+                                          '-Wmissing-field-initializers',
+                                          '-fno-strict-aliasing',
+                                          '-std=gnu99',
+                                          '-DHAVE_CONFIG_H']),
+                                      include_dirs=['../include/',
+                                                    '../lib/',
+                                                    '../ovn/lib',
+                                                    '../'],
+                                      sources=["ovs/_ovn-utils.c",
+                                               "../ovn/lib/lex.c",
+                                               "../ovn/lib/expr.c",
+                                               "../ovn/lib/actions.c"])],
     cmdclass={'build_ext': try_build_ext},
 )
 
diff --git a/tests/automake.mk b/tests/automake.mk
index 575ffeb..c45a5b5 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -94,7 +94,8 @@  TESTSUITE_AT = \
 	tests/ovn-nbctl.at \
 	tests/ovn-sbctl.at \
 	tests/ovn-controller.at \
-	tests/ovn-controller-vtep.at
+	tests/ovn-controller-vtep.at \
+	tests/test-ovn-utils.at
 
 SYSTEM_KMOD_TESTSUITE_AT = \
 	tests/system-common-macros.at \
@@ -385,6 +386,7 @@  CHECK_PYFILES = \
 	tests/test-jsonrpc.py \
 	tests/test-l7.py \
 	tests/test-ovsdb.py \
+	tests/test-ovn-utils.py \
 	tests/test-reconnect.py \
 	tests/MockXenAPI.py \
 	tests/test-unix-socket.py \
diff --git a/tests/test-ovn-utils.at b/tests/test-ovn-utils.at
new file mode 100644
index 0000000..66e5c36
--- /dev/null
+++ b/tests/test-ovn-utils.at
@@ -0,0 +1,15 @@ 
+AT_BANNER([Test OVN Utils])
+
+m4_define([TEST_OVN_UTILS],
+	[AT_SETUP([test-ovn-utils - $1])
+    AT_SKIP_IF([test $2 = no])
+    # Skip test if user doesn't have ovs.ovn_utils module
+    AT_SKIP_IF([! $3 -c 'import ovs.ovn_utils' 2> /dev/null])
+    AT_KEYWORDS([ovnutils])
+    AT_CHECK([$3 $srcdir/test-ovn-utils.py],
+             [0],[ignore], [ignore])
+    AT_CLEANUP])
+
+
+TEST_OVN_UTILS([Python2], [$HAVE_PYTHON], [$PYTHON])
+TEST_OVN_UTILS([Python3], [$HAVE_PYTHON3], [$PYTHON3])
diff --git a/tests/test-ovn-utils.py b/tests/test-ovn-utils.py
new file mode 100644
index 0000000..fecf2c0
--- /dev/null
+++ b/tests/test-ovn-utils.py
@@ -0,0 +1,33 @@ 
+# Copyright (c) 2016 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.
+
+import unittest
+
+from ovs import ovn_utils
+
+
+class TestOvnUtils(unittest.TestCase):
+
+    def test_parse_match_fail(self):
+        expected = "Syntax error at `a' expecting field name."
+        result = ovn_utils.parse_match("a")
+        self.assertEqual(result, expected)
+
+    def test_parse_match_success(self):
+        result = ovn_utils.parse_match(
+            'outport == "25560992-3f36-4a8b-bc19-e3f84188ef33" && ip4 && udp')
+        self.assertEqual(result, None)
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test-ovn.c b/tests/test-ovn.c
index ffb4e50..ba3fcc6 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -136,114 +136,6 @@  test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
 }
 
 static void
-create_symtab(struct shash *symtab)
-{
-    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_REG14, NULL);
-    expr_symtab_add_string(symtab, "outport", MFF_REG15, NULL);
-
-    expr_symtab_add_field(symtab, "xxreg0", MFF_XXREG0, NULL, false);
-    expr_symtab_add_field(symtab, "xxreg1", MFF_XXREG1, NULL, false);
-
-    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]");
-
-    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);
-
-    /* For negative testing. */
-    expr_symtab_add_field(symtab, "bad_prereq", MFF_XREG0, "xyzzy", false);
-    expr_symtab_add_field(symtab, "self_recurse", MFF_XREG0,
-                          "self_recurse != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_1", MFF_XREG0,
-                          "mutual_recurse_2 != 0", false);
-    expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
-                          "mutual_recurse_1 != 0", false);
-    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
-}
-
-static void
 create_dhcp_opts(struct hmap *dhcp_opts)
 {
     hmap_init(dhcp_opts);
@@ -311,7 +203,7 @@  test_parse_expr__(int steps)
     struct simap ports;
     struct ds input;
 
-    create_symtab(&symtab);
+    create_symtab_helper(&symtab);
     create_macros(&macros);
 
     simap_init(&ports);
@@ -1281,7 +1173,7 @@  test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
     struct simap ports, ct_zones;
     struct ds input;
 
-    create_symtab(&symtab);
+    create_symtab_helper(&symtab);
     create_dhcp_opts(&dhcp_opts);
 
     /* Initialize group ids. */
diff --git a/tests/testsuite.at b/tests/testsuite.at
index f5f1253..1721ba8 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -76,3 +76,4 @@  m4_include([tests/ovn-nbctl.at])
 m4_include([tests/ovn-sbctl.at])
 m4_include([tests/ovn-controller.at])
 m4_include([tests/ovn-controller-vtep.at])
+m4_include([tests/test-ovn-utils.at])