diff mbox

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

Message ID 1467322039-25138-1-git-send-email-aaronorosen@gmail.com
State Changes Requested
Headers show

Commit Message

YourName June 30, 2016, 9:27 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.
---
 ovn/lib/actions.c       | 105 ++++++++++++++++++++++++++++++++++++++++++++++++
 ovn/lib/actions.h       |   3 ++
 python/automake.mk      |   4 +-
 python/ovs/_ovn-utils.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++
 python/setup.py         |  31 +++++++++++++-
 tests/automake.mk       |   4 +-
 tests/test-ovn-utils.at |  13 ++++++
 tests/test-ovn-utils.py |  33 +++++++++++++++
 tests/test-ovn.c        | 104 -----------------------------------------------
 tests/testsuite.at      |   1 +
 10 files changed, 295 insertions(+), 107 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 June 30, 2016, 9:37 p.m. UTC | #1
Here's the v2 version of this (Sorry this is a new email message I was
hoping that this would be grouped together with the previous thread).

This patch adds python3 support and hooks in the testcases with autotest.

I was planning on reusing the existing test inputs that test-ovn parse-expr
uses but I decided against that because I only want parse_match() to return
something if there is an error otherwise just none - test-ovn parse-expr
returns the normalized version of the match which we don't need.

Let me know what you guys think of this patch when you get a chance.

Best,

Aaron



On Thu, Jun 30, 2016 at 2:27 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.
> ---
>  ovn/lib/actions.c       | 105
> ++++++++++++++++++++++++++++++++++++++++++++++++
>  ovn/lib/actions.h       |   3 ++
>  python/automake.mk      |   4 +-
>  python/ovs/_ovn-utils.c | 104
> +++++++++++++++++++++++++++++++++++++++++++++++
>  python/setup.py         |  31 +++++++++++++-
>  tests/automake.mk       |   4 +-
>  tests/test-ovn-utils.at |  13 ++++++
>  tests/test-ovn-utils.py |  33 +++++++++++++++
>  tests/test-ovn.c        | 104
> -----------------------------------------------
>  tests/testsuite.at      |   1 +
>  10 files changed, 295 insertions(+), 107 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/ovn/lib/actions.c b/ovn/lib/actions.c
> index 52c74e6..06f413d 100644
> --- a/ovn/lib/actions.c
> +++ b/ovn/lib/actions.c
> @@ -1002,3 +1002,108 @@ actions_parse_string(const char *s, const struct
> action_params *ap,
>
>      return error;
>  }
> +
> +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_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/ovn/lib/actions.h b/ovn/lib/actions.h
> index f49e15e..aaf0082 100644
> --- a/ovn/lib/actions.h
> +++ b/ovn/lib/actions.h
> @@ -111,4 +111,7 @@ char *actions_parse_string(const char *s, const struct
> action_params *,
>                             struct ofpbuf *ofpacts, struct expr **prereqsp)
>      OVS_WARN_UNUSED_RESULT;
>
> +void create_symtab(struct shash *symtab);
> +
> +
>  #endif /* ovn/actions.h */
> diff --git a/python/automake.mk b/python/automake.mk
> index ecad39d..64aabd9 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..d1b4193
> --- /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/lib/actions.h"
> +#include "ovn/lib/expr.h"
> +#include "ovn/lib/lex.h"
> +#include "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(&symtab);
> +    struct expr *expr;
> +    char *error;
> +    expr = expr_parse_string(match_string, &symtab, &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 8b24221..cf93e9f 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -92,7 +92,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 \
> @@ -381,6 +382,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..10a00b9
> --- /dev/null
> +++ b/tests/test-ovn-utils.at
> @@ -0,0 +1,13 @@
> +AT_BANNER([Test OVN Utils])
> +
> +m4_define([TEST_OVN_UTILS],
> +       [AT_SETUP([test-ovn-utils - $1])
> +    AT_SKIP_IF([test $2 = no])
> +    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 18e5aca..bcfcfa3 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -135,110 +135,6 @@ test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
>      ds_destroy(&output);
>  }
>
> -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_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);
> -}
>
>  static void
>  create_dhcp_opts(struct hmap *dhcp_opts)
> diff --git a/tests/testsuite.at b/tests/testsuite.at
> index 6b3fb25..f3f35aa 100644
> --- a/tests/testsuite.at
> +++ b/tests/testsuite.at
> @@ -75,3 +75,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])
> --
> 2.7.4
>
>
Ryan Moats July 1, 2016, 2:39 p.m. UTC | #2
"dev" <dev-bounces@openvswitch.org> wrote on 06/30/2016 04:27:19 PM:

> From: Aaron Rosen <aaronorosen@gmail.com>
> To: dev@openvswitch.org
> Cc: Aaron Rosen <aaronorosen@gmail.com>
> Date: 06/30/2016 04:29 PM
> Subject: [ovs-dev] [PATCH] ovn: expose c validator to python
> Sent by: "dev" <dev-bounces@openvswitch.org>
>
> 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.
> ---

As an FYI, if this is v2 of the patch, please indicate that in the
subject line by using the -v or --reroll-count option to
git-format-patch. That makes keeping track of superceded patches in
the patch queue easier...

Thanks in advance,
Ryan
YourName July 1, 2016, 8:21 p.m. UTC | #3
Yea sorry about that. I'll try and make sure I do that next time around.

Aaron

On Fri, Jul 1, 2016 at 7:39 AM, Ryan Moats <rmoats@us.ibm.com> wrote:

> "dev" <dev-bounces@openvswitch.org> wrote on 06/30/2016 04:27:19 PM:
>
> > From: Aaron Rosen <aaronorosen@gmail.com>
> > To: dev@openvswitch.org
> > Cc: Aaron Rosen <aaronorosen@gmail.com>
> > Date: 06/30/2016 04:29 PM
> > Subject: [ovs-dev] [PATCH] ovn: expose c validator to python
> > Sent by: "dev" <dev-bounces@openvswitch.org>
> >
> > 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.
> > ---
>
> As an FYI, if this is v2 of the patch, please indicate that in the
> subject line by using the -v or --reroll-count option to
> git-format-patch. That makes keeping track of superceded patches in
> the patch queue easier...
>
> Thanks in advance,
> Ryan
>
>
>
Ben Pfaff July 3, 2016, 7:04 p.m. UTC | #4
On Thu, Jun 30, 2016 at 02:27:19PM -0700, Aaron Rosen 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.

I'm OK with the idea of moving the code to create a symtab for testing
purposes into the OVN library, but I think that it should go into
expr.[ch] since that's what implements expressions, and it should have a
name and comment that makes it clear that it's for testing.

I don't yet understand how to use this, though.  When I run a normal
"make check" I get failures like this:

    #                             -*- compilation -*-
    2132. test-ovn-utils.at:12: testing test-ovn-utils - Python2 ...
    ../../tests/test-ovn-utils.at:12: $PYTHON $srcdir/test-ovn-utils.py
    stderr:
    Traceback (most recent call last):
      File "../../../../tests/test-ovn-utils.py", line 17, in <module>
        from ovs import ovn_utils
    ImportError: cannot import name ovn_utils
    stdout:
    ../../tests/test-ovn-utils.at:12: exit code was 1, expected 0
    2132. test-ovn-utils.at:12: 2132. test-ovn-utils - Python2 (test-ovn-utils.at:12): FAILED (test-ovn-utils.at:12)

Is some extra step required?

Thanks,

Ben.
YourName July 4, 2016, 7:17 p.m. UTC | #5
Thanks for the review. Response inline

On Sun, Jul 3, 2016 at 12:04 PM, Ben Pfaff <blp@ovn.org> wrote:

> On Thu, Jun 30, 2016 at 02:27:19PM -0700, Aaron Rosen 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.
>
> I'm OK with the idea of moving the code to create a symtab for testing
> purposes into the OVN library, but I think that it should go into
> expr.[ch] since that's what implements expressions, and it should have a
> name and comment that makes it clear that it's for testing.
>


Sounds good to me I'll make this update.


>
> I don't yet understand how to use this, though.  When I run a normal
> "make check" I get failures like this:
>
>     #                             -*- compilation -*-
>     2132. test-ovn-utils.at:12: testing test-ovn-utils - Python2 ...
>     ../../tests/test-ovn-utils.at:12: $PYTHON $srcdir/test-ovn-utils.py
>     stderr:
>     Traceback (most recent call last):
>       File "../../../../tests/test-ovn-utils.py", line 17, in <module>
>         from ovs import ovn_utils
>     ImportError: cannot import name ovn_utils
>     stdout:
>     ../../tests/test-ovn-utils.at:12: exit code was 1, expected 0
>     2132. test-ovn-utils.at:12: 2132. test-ovn-utils - Python2 (
> test-ovn-utils.at:12): FAILED (test-ovn-utils.at:12)
>
> Is some extra step required?


Looks like you need to run:

sudo python setup.py install
and
sudo python3 setup.py install (if you want the python3 tests to run).

inside of  ovs/python first.

Is there a good place were we could add a hook to make that occur?  I think
the other issue that we are facing before that is in order to build these C
extensions it currently is relying on the openvswitch-dev package being
installed on the system rather than able to use it from the current source
tree: https://github.com/openvswitch/ovs/blob/master/python/setup.py#L80

I believe Terry Wilson said that he was working on a fix for that though.





> Thanks,
>
> Ben.
>
Ben Pfaff July 13, 2016, 5:56 p.m. UTC | #6
On Mon, Jul 04, 2016 at 12:17:54PM -0700, Aaron Rosen wrote:
> On Sun, Jul 3, 2016 at 12:04 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> > On Thu, Jun 30, 2016 at 02:27:19PM -0700, Aaron Rosen 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.

> > I don't yet understand how to use this, though.  When I run a normal
> > "make check" I get failures like this:
> >
> >     #                             -*- compilation -*-
> >     2132. test-ovn-utils.at:12: testing test-ovn-utils - Python2 ...
> >     ../../tests/test-ovn-utils.at:12: $PYTHON $srcdir/test-ovn-utils.py
> >     stderr:
> >     Traceback (most recent call last):
> >       File "../../../../tests/test-ovn-utils.py", line 17, in <module>
> >         from ovs import ovn_utils
> >     ImportError: cannot import name ovn_utils
> >     stdout:
> >     ../../tests/test-ovn-utils.at:12: exit code was 1, expected 0
> >     2132. test-ovn-utils.at:12: 2132. test-ovn-utils - Python2 (
> > test-ovn-utils.at:12): FAILED (test-ovn-utils.at:12)
> >
> > Is some extra step required?
> 
> 
> Looks like you need to run:
> 
> sudo python setup.py install
> and
> sudo python3 setup.py install (if you want the python3 tests to run).
> 
> inside of  ovs/python first.
>
> Is there a good place were we could add a hook to make that occur?  I think
> the other issue that we are facing before that is in order to build these C
> extensions it currently is relying on the openvswitch-dev package being
> installed on the system rather than able to use it from the current source
> tree: https://github.com/openvswitch/ovs/blob/master/python/setup.py#L80

These changes are going to break the OVS build process, then.  Building
and testing OVS doesn't currently require root.  It sounds like a step
backwards to start requiring it.
YourName July 13, 2016, 9:11 p.m. UTC | #7
Yea I totally agree.

In the latest patch I sent out it skips these tests if the user doesn't
have the ovs python lib installed.  I'll try and see what I can do to fix
this.

On Wed, Jul 13, 2016 at 10:56 AM, Ben Pfaff <blp@ovn.org> wrote:

> On Mon, Jul 04, 2016 at 12:17:54PM -0700, Aaron Rosen wrote:
> > On Sun, Jul 3, 2016 at 12:04 PM, Ben Pfaff <blp@ovn.org> wrote:
> >
> > > On Thu, Jun 30, 2016 at 02:27:19PM -0700, Aaron Rosen 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.
>
> > > I don't yet understand how to use this, though.  When I run a normal
> > > "make check" I get failures like this:
> > >
> > >     #                             -*- compilation -*-
> > >     2132. test-ovn-utils.at:12: testing test-ovn-utils - Python2 ...
> > >     ../../tests/test-ovn-utils.at:12: $PYTHON
> $srcdir/test-ovn-utils.py
> > >     stderr:
> > >     Traceback (most recent call last):
> > >       File "../../../../tests/test-ovn-utils.py", line 17, in <module>
> > >         from ovs import ovn_utils
> > >     ImportError: cannot import name ovn_utils
> > >     stdout:
> > >     ../../tests/test-ovn-utils.at:12: exit code was 1, expected 0
> > >     2132. test-ovn-utils.at:12: 2132. test-ovn-utils - Python2 (
> > > test-ovn-utils.at:12): FAILED (test-ovn-utils.at:12)
> > >
> > > Is some extra step required?
> >
> >
> > Looks like you need to run:
> >
> > sudo python setup.py install
> > and
> > sudo python3 setup.py install (if you want the python3 tests to run).
> >
> > inside of  ovs/python first.
> >
> > Is there a good place were we could add a hook to make that occur?  I
> think
> > the other issue that we are facing before that is in order to build
> these C
> > extensions it currently is relying on the openvswitch-dev package being
> > installed on the system rather than able to use it from the current
> source
> > tree: https://github.com/openvswitch/ovs/blob/master/python/setup.py#L80
>
> These changes are going to break the OVS build process, then.  Building
> and testing OVS doesn't currently require root.  It sounds like a step
> backwards to start requiring it.
>
diff mbox

Patch

diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 52c74e6..06f413d 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -1002,3 +1002,108 @@  actions_parse_string(const char *s, const struct action_params *ap,
 
     return error;
 }
+
+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_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/ovn/lib/actions.h b/ovn/lib/actions.h
index f49e15e..aaf0082 100644
--- a/ovn/lib/actions.h
+++ b/ovn/lib/actions.h
@@ -111,4 +111,7 @@  char *actions_parse_string(const char *s, const struct action_params *,
                            struct ofpbuf *ofpacts, struct expr **prereqsp)
     OVS_WARN_UNUSED_RESULT;
 
+void create_symtab(struct shash *symtab);
+
+
 #endif /* ovn/actions.h */
diff --git a/python/automake.mk b/python/automake.mk
index ecad39d..64aabd9 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..d1b4193
--- /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/lib/actions.h"
+#include "ovn/lib/expr.h"
+#include "ovn/lib/lex.h"
+#include "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(&symtab);
+    struct expr *expr;
+    char *error;
+    expr = expr_parse_string(match_string, &symtab, &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 8b24221..cf93e9f 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -92,7 +92,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 \
@@ -381,6 +382,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..10a00b9
--- /dev/null
+++ b/tests/test-ovn-utils.at
@@ -0,0 +1,13 @@ 
+AT_BANNER([Test OVN Utils])
+
+m4_define([TEST_OVN_UTILS],
+	[AT_SETUP([test-ovn-utils - $1])
+    AT_SKIP_IF([test $2 = no])
+    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 18e5aca..bcfcfa3 100644
--- a/tests/test-ovn.c
+++ b/tests/test-ovn.c
@@ -135,110 +135,6 @@  test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
     ds_destroy(&output);
 }
 
-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_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);
-}
 
 static void
 create_dhcp_opts(struct hmap *dhcp_opts)
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 6b3fb25..f3f35aa 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -75,3 +75,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])