diff mbox series

[ovs-dev,v2,05/10] lflow-cache: Add unit tests.

Message ID 20210204132538.9958.40844.stgit@dceara.remote.csb
State Changes Requested
Headers show
Series ovn-controller: Make lflow cache size configurable. | expand

Commit Message

Dumitru Ceara Feb. 4, 2021, 1:25 p.m. UTC
Signed-off-by: Dumitru Ceara <dceara@redhat.com>
---
 controller/test-lflow-cache.c  |  224 +++++++++++++++++++++++++++++++++++
 controller/test-ofctrl-seqno.c |   18 ---
 tests/automake.mk              |    8 +
 tests/ovn-lflow-cache.at       |  257 ++++++++++++++++++++++++++++++++++++++++
 tests/ovn.at                   |   68 +++++++++++
 tests/test-utils.c             |   49 ++++++++
 tests/test-utils.h             |   26 ++++
 tests/testsuite.at             |    1 
 8 files changed, 633 insertions(+), 18 deletions(-)
 create mode 100644 controller/test-lflow-cache.c
 create mode 100644 tests/ovn-lflow-cache.at
 create mode 100644 tests/test-utils.c
 create mode 100644 tests/test-utils.h

Comments

Numan Siddique Feb. 9, 2021, 5:59 a.m. UTC | #1
On Thu, Feb 4, 2021 at 6:56 PM Dumitru Ceara <dceara@redhat.com> wrote:
>
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>

Acked-by: Numan Siddique <numans@ovn.org>

Numan

> ---
>  controller/test-lflow-cache.c  |  224 +++++++++++++++++++++++++++++++++++
>  controller/test-ofctrl-seqno.c |   18 ---
>  tests/automake.mk              |    8 +
>  tests/ovn-lflow-cache.at       |  257 ++++++++++++++++++++++++++++++++++++++++
>  tests/ovn.at                   |   68 +++++++++++
>  tests/test-utils.c             |   49 ++++++++
>  tests/test-utils.h             |   26 ++++
>  tests/testsuite.at             |    1
>  8 files changed, 633 insertions(+), 18 deletions(-)
>  create mode 100644 controller/test-lflow-cache.c
>  create mode 100644 tests/ovn-lflow-cache.at
>  create mode 100644 tests/test-utils.c
>  create mode 100644 tests/test-utils.h
>
> diff --git a/controller/test-lflow-cache.c b/controller/test-lflow-cache.c
> new file mode 100644
> index 0000000..d6e962e
> --- /dev/null
> +++ b/controller/test-lflow-cache.c
> @@ -0,0 +1,224 @@
> +/* Copyright (c) 2021, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "ovn/expr.h"
> +#include "ovn-sb-idl.h"
> +#include "tests/ovstest.h"
> +#include "tests/test-utils.h"
> +#include "util.h"
> +
> +#include "lflow-cache.h"
> +
> +static void
> +test_lflow_cache_add__(struct lflow_cache *lc, const char *op_type,
> +                       const struct sbrec_logical_flow *lflow,
> +                       unsigned int conj_id_ofs,
> +                       struct expr *e)
> +{
> +    printf("ADD %s:\n", op_type);
> +    printf("  conj-id-ofs: %u\n", conj_id_ofs);
> +
> +    if (!strcmp(op_type, "conj-id")) {
> +        lflow_cache_add_conj_id(lc, lflow, conj_id_ofs);
> +    } else if (!strcmp(op_type, "expr")) {
> +        lflow_cache_add_expr(lc, lflow, conj_id_ofs, expr_clone(e));
> +    } else if (!strcmp(op_type, "matches")) {
> +        struct hmap *matches = xmalloc(sizeof *matches);
> +        ovs_assert(expr_to_matches(e, NULL, NULL, matches) == 0);
> +        ovs_assert(hmap_count(matches) == 1);
> +        lflow_cache_add_matches(lc, lflow, matches);
> +    } else {
> +        OVS_NOT_REACHED();
> +    }
> +}
> +
> +static void
> +test_lflow_cache_lookup__(struct lflow_cache *lc,
> +                          const struct sbrec_logical_flow *lflow)
> +{
> +    struct lflow_cache_value *lcv = lflow_cache_get(lc, lflow);
> +
> +    printf("LOOKUP:\n");
> +    if (!lcv) {
> +        printf("  not found\n");
> +        return;
> +    }
> +
> +    printf("  conj_id_ofs: %"PRIu32"\n", lcv->conj_id_ofs);
> +    switch (lcv->type) {
> +    case LCACHE_T_CONJ_ID:
> +        printf("  type: conj-id\n");
> +        break;
> +    case LCACHE_T_EXPR:
> +        printf("  type: expr\n");
> +        break;
> +    case LCACHE_T_MATCHES:
> +        printf("  type: matches\n");
> +        break;
> +    case LCACHE_T_NONE:
> +        OVS_NOT_REACHED();
> +        break;
> +    }
> +}
> +
> +static void
> +test_lflow_cache_delete__(struct lflow_cache *lc,
> +                          const struct sbrec_logical_flow *lflow)
> +{
> +    printf("DELETE\n");
> +    lflow_cache_delete(lc, lflow);
> +}
> +
> +static void
> +test_lflow_cache_stats__(struct lflow_cache *lc)
> +{
> +    struct lflow_cache_stats *lcs = lflow_cache_get_stats(lc);
> +
> +    if (!lcs) {
> +        return;
> +    }
> +    printf("Enabled: %s\n", lflow_cache_is_enabled(lc) ? "true" : "false");
> +    for (size_t i = 0; i < LCACHE_T_MAX; i++) {
> +        printf("  %s: %"PRIuSIZE"\n", lflow_cache_type_names[i],
> +               lcs->n_entries[i]);
> +    }
> +    free(lcs);
> +}
> +
> +static void
> +test_lflow_cache_operations(struct ovs_cmdl_context *ctx)
> +{
> +    struct lflow_cache *lc = lflow_cache_create();
> +    struct expr *e = expr_create_boolean(true);
> +    bool enabled = !strcmp(ctx->argv[1], "true");
> +    unsigned int shift = 2;
> +    unsigned int n_ops;
> +
> +    lflow_cache_enable(lc, enabled);
> +    test_lflow_cache_stats__(lc);
> +
> +    if (!test_read_uint_value(ctx, shift++, "n_ops", &n_ops)) {
> +        goto done;
> +    }
> +
> +    for (unsigned int i = 0; i < n_ops; i++) {
> +        const char *op = test_read_value(ctx, shift++, "op");
> +
> +        if (!op) {
> +            goto done;
> +        }
> +
> +        struct sbrec_logical_flow lflow;
> +        uuid_generate(&lflow.header_.uuid);
> +
> +        if (!strcmp(op, "add")) {
> +            const char *op_type = test_read_value(ctx, shift++, "op_type");
> +            if (!op_type) {
> +                goto done;
> +            }
> +
> +            unsigned int conj_id_ofs;
> +            if (!test_read_uint_value(ctx, shift++, "conj-id-ofs",
> +                                      &conj_id_ofs)) {
> +                goto done;
> +            }
> +
> +            test_lflow_cache_add__(lc, op_type, &lflow, conj_id_ofs, e);
> +            test_lflow_cache_lookup__(lc, &lflow);
> +        } else if (!strcmp(op, "add-del")) {
> +            const char *op_type = test_read_value(ctx, shift++, "op_type");
> +            if (!op_type) {
> +                goto done;
> +            }
> +
> +            unsigned int conj_id_ofs;
> +            if (!test_read_uint_value(ctx, shift++, "conj-id-ofs",
> +                                      &conj_id_ofs)) {
> +                goto done;
> +            }
> +
> +            test_lflow_cache_add__(lc, op_type, &lflow, conj_id_ofs, e);
> +            test_lflow_cache_lookup__(lc, &lflow);
> +            test_lflow_cache_delete__(lc, &lflow);
> +            test_lflow_cache_lookup__(lc, &lflow);
> +        } else if (!strcmp(op, "enable")) {
> +            printf("ENABLE\n");
> +            lflow_cache_enable(lc, true);
> +        } else if (!strcmp(op, "disable")) {
> +            printf("DISABLE\n");
> +            lflow_cache_enable(lc, false);
> +        } else if (!strcmp(op, "flush")) {
> +            printf("FLUSH\n");
> +            lflow_cache_flush(lc);
> +        } else {
> +            OVS_NOT_REACHED();
> +        }
> +        test_lflow_cache_stats__(lc);
> +    }
> +done:
> +    lflow_cache_destroy(lc);
> +    expr_destroy(e);
> +}
> +
> +static void
> +test_lflow_cache_negative(struct ovs_cmdl_context *ctx OVS_UNUSED)
> +{
> +    lflow_cache_flush(NULL);
> +    lflow_cache_destroy(NULL);
> +    lflow_cache_enable(NULL, true);
> +    ovs_assert(!lflow_cache_is_enabled(NULL));
> +    ovs_assert(!lflow_cache_get_stats(NULL));
> +
> +    struct lflow_cache *lcs[] = {
> +        NULL,
> +        lflow_cache_create(),
> +    };
> +
> +    for (size_t i = 0; i < ARRAY_SIZE(lcs); i++) {
> +        struct expr *e = expr_create_boolean(true);
> +        struct hmap *matches = xmalloc(sizeof *matches);
> +
> +        ovs_assert(expr_to_matches(e, NULL, NULL, matches) == 0);
> +        ovs_assert(hmap_count(matches) == 1);
> +
> +        lflow_cache_add_conj_id(lcs[i], NULL, 0);
> +        lflow_cache_add_expr(lcs[i], NULL, 0, NULL);
> +        lflow_cache_add_expr(lcs[i], NULL, 0, e);
> +        lflow_cache_add_matches(lcs[i], NULL, NULL);
> +        lflow_cache_add_matches(lcs[i], NULL, matches);
> +        lflow_cache_destroy(lcs[i]);
> +    }
> +}
> +
> +static void
> +test_lflow_cache_main(int argc, char *argv[])
> +{
> +    set_program_name(argv[0]);
> +    static const struct ovs_cmdl_command commands[] = {
> +        {"lflow_cache_operations", NULL, 3, INT_MAX,
> +         test_lflow_cache_operations, OVS_RO},
> +        {"lflow_cache_negative", NULL, 0, 0,
> +         test_lflow_cache_negative, OVS_RO},
> +        {NULL, NULL, 0, 0, NULL, OVS_RO},
> +    };
> +    struct ovs_cmdl_context ctx;
> +    ctx.argc = argc - 1;
> +    ctx.argv = argv + 1;
> +    ovs_cmdl_run_command(&ctx, commands);
> +}
> +
> +OVSTEST_REGISTER("test-lflow-cache", test_lflow_cache_main);
> diff --git a/controller/test-ofctrl-seqno.c b/controller/test-ofctrl-seqno.c
> index fce88d4..b96da9d 100644
> --- a/controller/test-ofctrl-seqno.c
> +++ b/controller/test-ofctrl-seqno.c
> @@ -16,6 +16,7 @@
>  #include <config.h>
>
>  #include "tests/ovstest.h"
> +#include "tests/test-utils.h"
>  #include "sort.h"
>  #include "util.h"
>
> @@ -27,23 +28,6 @@ test_init(void)
>      ofctrl_seqno_init();
>  }
>
> -static bool
> -test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
> -                     const char *descr, unsigned int *result)
> -{
> -    if (index >= ctx->argc) {
> -        fprintf(stderr, "Missing %s argument\n", descr);
> -        return false;
> -    }
> -
> -    const char *arg = ctx->argv[index];
> -    if (!str_to_uint(arg, 10, result)) {
> -        fprintf(stderr, "Invalid %s: %s\n", descr, arg);
> -        return false;
> -    }
> -    return true;
> -}
> -
>  static int
>  test_seqno_compare(size_t a, size_t b, void *values_)
>  {
> diff --git a/tests/automake.mk b/tests/automake.mk
> index 282d1b1..df6d0a2 100644
> --- a/tests/automake.mk
> +++ b/tests/automake.mk
> @@ -33,7 +33,8 @@ TESTSUITE_AT = \
>         tests/ovn-macros.at \
>         tests/ovn-performance.at \
>         tests/ovn-ofctrl-seqno.at \
> -       tests/ovn-ipam.at
> +       tests/ovn-ipam.at \
> +       tests/ovn-lflow-cache.at
>
>  SYSTEM_KMOD_TESTSUITE_AT = \
>         tests/system-common-macros.at \
> @@ -207,8 +208,13 @@ noinst_PROGRAMS += tests/ovstest
>  tests_ovstest_SOURCES = \
>         tests/ovstest.c \
>         tests/ovstest.h \
> +       tests/test-utils.c \
> +       tests/test-utils.h \
>         tests/test-ovn.c \
> +       controller/test-lflow-cache.c \
>         controller/test-ofctrl-seqno.c \
> +       controller/lflow-cache.c \
> +       controller/lflow-cache.h \
>         controller/ofctrl-seqno.c \
>         controller/ofctrl-seqno.h \
>         northd/test-ipam.c \
> diff --git a/tests/ovn-lflow-cache.at b/tests/ovn-lflow-cache.at
> new file mode 100644
> index 0000000..f7e8959
> --- /dev/null
> +++ b/tests/ovn-lflow-cache.at
> @@ -0,0 +1,257 @@
> +#
> +# Unit tests for the controller/lflow-cache.c module.
> +#
> +AT_BANNER([OVN unit tests - lflow-cache])
> +
> +AT_SETUP([ovn -- unit test -- lflow-cache single add/lookup])
> +AT_CHECK(
> +    [ovstest test-lflow-cache lflow_cache_operations \
> +        true 3 \
> +        add conj-id 1 \
> +        add expr 2 \
> +        add matches 3],
> +    [0], [dnl
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 1
> +LOOKUP:
> +  conj_id_ofs: 1
> +  type: conj-id
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 2
> +LOOKUP:
> +  conj_id_ofs: 2
> +  type: expr
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 3
> +LOOKUP:
> +  conj_id_ofs: 0
> +  type: matches
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 1
> +])
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- unit test -- lflow-cache single add/lookup/del])
> +AT_CHECK(
> +    [ovstest test-lflow-cache lflow_cache_operations \
> +        true 3 \
> +        add-del conj-id 1 \
> +        add-del expr 2 \
> +        add-del matches 3],
> +    [0], [dnl
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 1
> +LOOKUP:
> +  conj_id_ofs: 1
> +  type: conj-id
> +DELETE
> +LOOKUP:
> +  not found
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 2
> +LOOKUP:
> +  conj_id_ofs: 2
> +  type: expr
> +DELETE
> +LOOKUP:
> +  not found
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 3
> +LOOKUP:
> +  conj_id_ofs: 0
> +  type: matches
> +DELETE
> +LOOKUP:
> +  not found
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +])
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- unit test -- lflow-cache disabled single add/lookup/del])
> +AT_CHECK(
> +    [ovstest test-lflow-cache lflow_cache_operations \
> +        false 3 \
> +        add conj-id 1 \
> +        add expr 2 \
> +        add matches 3],
> +    [0], [dnl
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 1
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 2
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 3
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +])
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- unit test -- lflow-cache disable/enable/flush])
> +AT_CHECK(
> +    [ovstest test-lflow-cache lflow_cache_operations \
> +        true 12 \
> +        add conj-id 1 \
> +        add expr 2 \
> +        add matches 3 \
> +        disable \
> +        add conj-id 4 \
> +        add expr 5 \
> +        add matches 6 \
> +        enable \
> +        add conj-id 7 \
> +        add expr 8 \
> +        add matches 9 \
> +        flush],
> +    [0], [dnl
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 1
> +LOOKUP:
> +  conj_id_ofs: 1
> +  type: conj-id
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 2
> +LOOKUP:
> +  conj_id_ofs: 2
> +  type: expr
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 3
> +LOOKUP:
> +  conj_id_ofs: 0
> +  type: matches
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 1
> +DISABLE
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 4
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 5
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 6
> +LOOKUP:
> +  not found
> +Enabled: false
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ENABLE
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD conj-id:
> +  conj-id-ofs: 7
> +LOOKUP:
> +  conj_id_ofs: 7
> +  type: conj-id
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 0
> +  cache-matches: 0
> +ADD expr:
> +  conj-id-ofs: 8
> +LOOKUP:
> +  conj_id_ofs: 8
> +  type: expr
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 0
> +ADD matches:
> +  conj-id-ofs: 9
> +LOOKUP:
> +  conj_id_ofs: 0
> +  type: matches
> +Enabled: true
> +  cache-conj-id: 1
> +  cache-expr: 1
> +  cache-matches: 1
> +FLUSH
> +Enabled: true
> +  cache-conj-id: 0
> +  cache-expr: 0
> +  cache-matches: 0
> +])
> +AT_CLEANUP
> +
> +AT_SETUP([ovn -- unit test -- lflow-cache negative tests])
> +AT_CHECK([ovstest test-lflow-cache lflow_cache_negative], [0], [])
> +AT_CLEANUP
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 4a75acc..0e114cf 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -22235,6 +22235,74 @@ OVN_CLEANUP([hv1])
>
>  AT_CLEANUP
>
> +AT_SETUP([ovn -- lflow cache operations])
> +ovn_start
> +net_add n1
> +sim_add hv1
> +
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +
> +as hv1
> +ovs-vsctl -- add-port br-int hv1-vif1 \
> +    -- set interface hv1-vif1 external-ids:iface-id=lsp1 \
> +    -- add-port br-int hv1-vif2 \
> +    -- set interface hv1-vif2 external-ids:iface-id=lsp2
> +
> +ovn-nbctl ls-add ls1 \
> +    -- lsp-add ls1 lsp1 \
> +    -- lsp-add ls1 lsp2 \
> +    -- pg-add pg1 lsp1 lsp2 \
> +    -- create Address_Set name=as1 addresses=\"10.0.0.1\",\"10.0.0.2\"
> +check ovn-nbctl --wait=hv sync
> +wait_for_ports_up lsp1 lsp2
> +
> +get_cache_count () {
> +    local cache_name=$1
> +    as hv1 ovn-appctl -t ovn-controller lflow-cache/show-stats | grep ${cache_name} | awk '{ print $3 }'
> +}
> +
> +AS_BOX([Check matches caching])
> +conj_id_cnt=$(get_cache_count cache-conj-id)
> +expr_cnt=$(get_cache_count cache-expr)
> +matches_cnt=$(get_cache_count cache-matches)
> +
> +check ovn-nbctl acl-add ls1 from-lport 1 '1' drop
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test "$conj_id_cnt" = "$(get_cache_count cache-conj-id)"], [0], [])
> +AT_CHECK([test "$expr_cnt" = "$(get_cache_count cache-expr)"], [0], [])
> +AT_CHECK([test "$(($matches_cnt + 1))" = "$(get_cache_count cache-matches)"], [0], [])
> +
> +AS_BOX([Check expr caching for is_chassis_resident() matches])
> +conj_id_cnt=$(get_cache_count cache-conj-id)
> +expr_cnt=$(get_cache_count cache-expr)
> +matches_cnt=$(get_cache_count cache-matches)
> +
> +check ovn-nbctl acl-add ls1 from-lport 1 'is_chassis_resident("lsp1")' drop
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test "$conj_id_cnt" = "$(get_cache_count cache-conj-id)"], [0], [])
> +AT_CHECK([test "$(($expr_cnt + 1))" = "$(get_cache_count cache-expr)"], [0], [])
> +AT_CHECK([test "$matches_cnt" = "$(get_cache_count cache-matches)"], [0], [])
> +
> +AS_BOX([Check conj-id caching for conjunctive port group/address set matches])
> +conj_id_cnt=$(get_cache_count cache-conj-id)
> +expr_cnt=$(get_cache_count cache-expr)
> +matches_cnt=$(get_cache_count cache-matches)
> +
> +check ovn-nbctl acl-add ls1 from-lport 1 'inport == @pg1 && outport == @pg1 && is_chassis_resident("lsp1")' drop
> +check ovn-nbctl acl-add ls1 from-lport 1 'ip4.src == $as1 && ip4.dst == $as1 && is_chassis_resident("lsp1")' drop
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([test "$(($conj_id_cnt + 2))" = "$(get_cache_count cache-conj-id)"], [0], [])
> +AT_CHECK([test "$expr_cnt" = "$(get_cache_count cache-expr)"], [0], [])
> +AT_CHECK([test "$matches_cnt" = "$(get_cache_count cache-matches)"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +
>  AT_SETUP([ovn -- Delete Port_Binding and OVS port Incremental Processing])
>  ovn_start
>
> diff --git a/tests/test-utils.c b/tests/test-utils.c
> new file mode 100644
> index 0000000..6a3b198
> --- /dev/null
> +++ b/tests/test-utils.c
> @@ -0,0 +1,49 @@
> +/* Copyright (c) 2021, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "test-utils.h"
> +
> +#include "util.h"
> +
> +bool
> +test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
> +                     const char *descr, unsigned int *result)
> +{
> +    if (index >= ctx->argc) {
> +        fprintf(stderr, "Missing %s argument\n", descr);
> +        return false;
> +    }
> +
> +    const char *arg = ctx->argv[index];
> +    if (!str_to_uint(arg, 10, result)) {
> +        fprintf(stderr, "Invalid %s: %s\n", descr, arg);
> +        return false;
> +    }
> +    return true;
> +}
> +
> +const char *
> +test_read_value(struct ovs_cmdl_context *ctx, unsigned int index,
> +                const char *descr)
> +{
> +    if (index >= ctx->argc) {
> +        fprintf(stderr, "Missing %s argument\n", descr);
> +        return NULL;
> +    }
> +
> +    return ctx->argv[index];
> +}
> diff --git a/tests/test-utils.h b/tests/test-utils.h
> new file mode 100644
> index 0000000..721032f
> --- /dev/null
> +++ b/tests/test-utils.h
> @@ -0,0 +1,26 @@
> +/* Copyright (c) 2021, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef TEST_UTILS_H
> +#define TEST_UTILS_H 1
> +
> +#include "ovstest.h"
> +
> +bool test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
> +                          const char *descr, unsigned int *result);
> +const char *test_read_value(struct ovs_cmdl_context *ctx, unsigned int index,
> +                            const char *descr);
> +
> +#endif /* tests/test-utils.h */
> diff --git a/tests/testsuite.at b/tests/testsuite.at
> index 6253d11..35908ad 100644
> --- a/tests/testsuite.at
> +++ b/tests/testsuite.at
> @@ -27,6 +27,7 @@ m4_include([tests/ovn.at])
>  m4_include([tests/ovn-performance.at])
>  m4_include([tests/ovn-northd.at])
>  m4_include([tests/ovn-nbctl.at])
> +m4_include([tests/ovn-lflow-cache.at])
>  m4_include([tests/ovn-ofctrl-seqno.at])
>  m4_include([tests/ovn-sbctl.at])
>  m4_include([tests/ovn-ic-nbctl.at])
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/test-lflow-cache.c b/controller/test-lflow-cache.c
new file mode 100644
index 0000000..d6e962e
--- /dev/null
+++ b/controller/test-lflow-cache.c
@@ -0,0 +1,224 @@ 
+/* Copyright (c) 2021, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovn/expr.h"
+#include "ovn-sb-idl.h"
+#include "tests/ovstest.h"
+#include "tests/test-utils.h"
+#include "util.h"
+
+#include "lflow-cache.h"
+
+static void
+test_lflow_cache_add__(struct lflow_cache *lc, const char *op_type,
+                       const struct sbrec_logical_flow *lflow,
+                       unsigned int conj_id_ofs,
+                       struct expr *e)
+{
+    printf("ADD %s:\n", op_type);
+    printf("  conj-id-ofs: %u\n", conj_id_ofs);
+
+    if (!strcmp(op_type, "conj-id")) {
+        lflow_cache_add_conj_id(lc, lflow, conj_id_ofs);
+    } else if (!strcmp(op_type, "expr")) {
+        lflow_cache_add_expr(lc, lflow, conj_id_ofs, expr_clone(e));
+    } else if (!strcmp(op_type, "matches")) {
+        struct hmap *matches = xmalloc(sizeof *matches);
+        ovs_assert(expr_to_matches(e, NULL, NULL, matches) == 0);
+        ovs_assert(hmap_count(matches) == 1);
+        lflow_cache_add_matches(lc, lflow, matches);
+    } else {
+        OVS_NOT_REACHED();
+    }
+}
+
+static void
+test_lflow_cache_lookup__(struct lflow_cache *lc,
+                          const struct sbrec_logical_flow *lflow)
+{
+    struct lflow_cache_value *lcv = lflow_cache_get(lc, lflow);
+
+    printf("LOOKUP:\n");
+    if (!lcv) {
+        printf("  not found\n");
+        return;
+    }
+
+    printf("  conj_id_ofs: %"PRIu32"\n", lcv->conj_id_ofs);
+    switch (lcv->type) {
+    case LCACHE_T_CONJ_ID:
+        printf("  type: conj-id\n");
+        break;
+    case LCACHE_T_EXPR:
+        printf("  type: expr\n");
+        break;
+    case LCACHE_T_MATCHES:
+        printf("  type: matches\n");
+        break;
+    case LCACHE_T_NONE:
+        OVS_NOT_REACHED();
+        break;
+    }
+}
+
+static void
+test_lflow_cache_delete__(struct lflow_cache *lc,
+                          const struct sbrec_logical_flow *lflow)
+{
+    printf("DELETE\n");
+    lflow_cache_delete(lc, lflow);
+}
+
+static void
+test_lflow_cache_stats__(struct lflow_cache *lc)
+{
+    struct lflow_cache_stats *lcs = lflow_cache_get_stats(lc);
+
+    if (!lcs) {
+        return;
+    }
+    printf("Enabled: %s\n", lflow_cache_is_enabled(lc) ? "true" : "false");
+    for (size_t i = 0; i < LCACHE_T_MAX; i++) {
+        printf("  %s: %"PRIuSIZE"\n", lflow_cache_type_names[i],
+               lcs->n_entries[i]);
+    }
+    free(lcs);
+}
+
+static void
+test_lflow_cache_operations(struct ovs_cmdl_context *ctx)
+{
+    struct lflow_cache *lc = lflow_cache_create();
+    struct expr *e = expr_create_boolean(true);
+    bool enabled = !strcmp(ctx->argv[1], "true");
+    unsigned int shift = 2;
+    unsigned int n_ops;
+
+    lflow_cache_enable(lc, enabled);
+    test_lflow_cache_stats__(lc);
+
+    if (!test_read_uint_value(ctx, shift++, "n_ops", &n_ops)) {
+        goto done;
+    }
+
+    for (unsigned int i = 0; i < n_ops; i++) {
+        const char *op = test_read_value(ctx, shift++, "op");
+
+        if (!op) {
+            goto done;
+        }
+
+        struct sbrec_logical_flow lflow;
+        uuid_generate(&lflow.header_.uuid);
+
+        if (!strcmp(op, "add")) {
+            const char *op_type = test_read_value(ctx, shift++, "op_type");
+            if (!op_type) {
+                goto done;
+            }
+
+            unsigned int conj_id_ofs;
+            if (!test_read_uint_value(ctx, shift++, "conj-id-ofs",
+                                      &conj_id_ofs)) {
+                goto done;
+            }
+
+            test_lflow_cache_add__(lc, op_type, &lflow, conj_id_ofs, e);
+            test_lflow_cache_lookup__(lc, &lflow);
+        } else if (!strcmp(op, "add-del")) {
+            const char *op_type = test_read_value(ctx, shift++, "op_type");
+            if (!op_type) {
+                goto done;
+            }
+
+            unsigned int conj_id_ofs;
+            if (!test_read_uint_value(ctx, shift++, "conj-id-ofs",
+                                      &conj_id_ofs)) {
+                goto done;
+            }
+
+            test_lflow_cache_add__(lc, op_type, &lflow, conj_id_ofs, e);
+            test_lflow_cache_lookup__(lc, &lflow);
+            test_lflow_cache_delete__(lc, &lflow);
+            test_lflow_cache_lookup__(lc, &lflow);
+        } else if (!strcmp(op, "enable")) {
+            printf("ENABLE\n");
+            lflow_cache_enable(lc, true);
+        } else if (!strcmp(op, "disable")) {
+            printf("DISABLE\n");
+            lflow_cache_enable(lc, false);
+        } else if (!strcmp(op, "flush")) {
+            printf("FLUSH\n");
+            lflow_cache_flush(lc);
+        } else {
+            OVS_NOT_REACHED();
+        }
+        test_lflow_cache_stats__(lc);
+    }
+done:
+    lflow_cache_destroy(lc);
+    expr_destroy(e);
+}
+
+static void
+test_lflow_cache_negative(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+    lflow_cache_flush(NULL);
+    lflow_cache_destroy(NULL);
+    lflow_cache_enable(NULL, true);
+    ovs_assert(!lflow_cache_is_enabled(NULL));
+    ovs_assert(!lflow_cache_get_stats(NULL));
+
+    struct lflow_cache *lcs[] = {
+        NULL,
+        lflow_cache_create(),
+    };
+
+    for (size_t i = 0; i < ARRAY_SIZE(lcs); i++) {
+        struct expr *e = expr_create_boolean(true);
+        struct hmap *matches = xmalloc(sizeof *matches);
+
+        ovs_assert(expr_to_matches(e, NULL, NULL, matches) == 0);
+        ovs_assert(hmap_count(matches) == 1);
+
+        lflow_cache_add_conj_id(lcs[i], NULL, 0);
+        lflow_cache_add_expr(lcs[i], NULL, 0, NULL);
+        lflow_cache_add_expr(lcs[i], NULL, 0, e);
+        lflow_cache_add_matches(lcs[i], NULL, NULL);
+        lflow_cache_add_matches(lcs[i], NULL, matches);
+        lflow_cache_destroy(lcs[i]);
+    }
+}
+
+static void
+test_lflow_cache_main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    static const struct ovs_cmdl_command commands[] = {
+        {"lflow_cache_operations", NULL, 3, INT_MAX,
+         test_lflow_cache_operations, OVS_RO},
+        {"lflow_cache_negative", NULL, 0, 0,
+         test_lflow_cache_negative, OVS_RO},
+        {NULL, NULL, 0, 0, NULL, OVS_RO},
+    };
+    struct ovs_cmdl_context ctx;
+    ctx.argc = argc - 1;
+    ctx.argv = argv + 1;
+    ovs_cmdl_run_command(&ctx, commands);
+}
+
+OVSTEST_REGISTER("test-lflow-cache", test_lflow_cache_main);
diff --git a/controller/test-ofctrl-seqno.c b/controller/test-ofctrl-seqno.c
index fce88d4..b96da9d 100644
--- a/controller/test-ofctrl-seqno.c
+++ b/controller/test-ofctrl-seqno.c
@@ -16,6 +16,7 @@ 
 #include <config.h>
 
 #include "tests/ovstest.h"
+#include "tests/test-utils.h"
 #include "sort.h"
 #include "util.h"
 
@@ -27,23 +28,6 @@  test_init(void)
     ofctrl_seqno_init();
 }
 
-static bool
-test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
-                     const char *descr, unsigned int *result)
-{
-    if (index >= ctx->argc) {
-        fprintf(stderr, "Missing %s argument\n", descr);
-        return false;
-    }
-
-    const char *arg = ctx->argv[index];
-    if (!str_to_uint(arg, 10, result)) {
-        fprintf(stderr, "Invalid %s: %s\n", descr, arg);
-        return false;
-    }
-    return true;
-}
-
 static int
 test_seqno_compare(size_t a, size_t b, void *values_)
 {
diff --git a/tests/automake.mk b/tests/automake.mk
index 282d1b1..df6d0a2 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -33,7 +33,8 @@  TESTSUITE_AT = \
 	tests/ovn-macros.at \
 	tests/ovn-performance.at \
 	tests/ovn-ofctrl-seqno.at \
-	tests/ovn-ipam.at
+	tests/ovn-ipam.at \
+	tests/ovn-lflow-cache.at
 
 SYSTEM_KMOD_TESTSUITE_AT = \
 	tests/system-common-macros.at \
@@ -207,8 +208,13 @@  noinst_PROGRAMS += tests/ovstest
 tests_ovstest_SOURCES = \
 	tests/ovstest.c \
 	tests/ovstest.h \
+	tests/test-utils.c \
+	tests/test-utils.h \
 	tests/test-ovn.c \
+	controller/test-lflow-cache.c \
 	controller/test-ofctrl-seqno.c \
+	controller/lflow-cache.c \
+	controller/lflow-cache.h \
 	controller/ofctrl-seqno.c \
 	controller/ofctrl-seqno.h \
 	northd/test-ipam.c \
diff --git a/tests/ovn-lflow-cache.at b/tests/ovn-lflow-cache.at
new file mode 100644
index 0000000..f7e8959
--- /dev/null
+++ b/tests/ovn-lflow-cache.at
@@ -0,0 +1,257 @@ 
+#
+# Unit tests for the controller/lflow-cache.c module.
+#
+AT_BANNER([OVN unit tests - lflow-cache])
+
+AT_SETUP([ovn -- unit test -- lflow-cache single add/lookup])
+AT_CHECK(
+    [ovstest test-lflow-cache lflow_cache_operations \
+        true 3 \
+        add conj-id 1 \
+        add expr 2 \
+        add matches 3],
+    [0], [dnl
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 1
+LOOKUP:
+  conj_id_ofs: 1
+  type: conj-id
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 2
+LOOKUP:
+  conj_id_ofs: 2
+  type: expr
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 3
+LOOKUP:
+  conj_id_ofs: 0
+  type: matches
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 1
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- unit test -- lflow-cache single add/lookup/del])
+AT_CHECK(
+    [ovstest test-lflow-cache lflow_cache_operations \
+        true 3 \
+        add-del conj-id 1 \
+        add-del expr 2 \
+        add-del matches 3],
+    [0], [dnl
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 1
+LOOKUP:
+  conj_id_ofs: 1
+  type: conj-id
+DELETE
+LOOKUP:
+  not found
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 2
+LOOKUP:
+  conj_id_ofs: 2
+  type: expr
+DELETE
+LOOKUP:
+  not found
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 3
+LOOKUP:
+  conj_id_ofs: 0
+  type: matches
+DELETE
+LOOKUP:
+  not found
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- unit test -- lflow-cache disabled single add/lookup/del])
+AT_CHECK(
+    [ovstest test-lflow-cache lflow_cache_operations \
+        false 3 \
+        add conj-id 1 \
+        add expr 2 \
+        add matches 3],
+    [0], [dnl
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 1
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 2
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 3
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- unit test -- lflow-cache disable/enable/flush])
+AT_CHECK(
+    [ovstest test-lflow-cache lflow_cache_operations \
+        true 12 \
+        add conj-id 1 \
+        add expr 2 \
+        add matches 3 \
+        disable \
+        add conj-id 4 \
+        add expr 5 \
+        add matches 6 \
+        enable \
+        add conj-id 7 \
+        add expr 8 \
+        add matches 9 \
+        flush],
+    [0], [dnl
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 1
+LOOKUP:
+  conj_id_ofs: 1
+  type: conj-id
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 2
+LOOKUP:
+  conj_id_ofs: 2
+  type: expr
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 3
+LOOKUP:
+  conj_id_ofs: 0
+  type: matches
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 1
+DISABLE
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 4
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 5
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 6
+LOOKUP:
+  not found
+Enabled: false
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ENABLE
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+ADD conj-id:
+  conj-id-ofs: 7
+LOOKUP:
+  conj_id_ofs: 7
+  type: conj-id
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 0
+  cache-matches: 0
+ADD expr:
+  conj-id-ofs: 8
+LOOKUP:
+  conj_id_ofs: 8
+  type: expr
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 0
+ADD matches:
+  conj-id-ofs: 9
+LOOKUP:
+  conj_id_ofs: 0
+  type: matches
+Enabled: true
+  cache-conj-id: 1
+  cache-expr: 1
+  cache-matches: 1
+FLUSH
+Enabled: true
+  cache-conj-id: 0
+  cache-expr: 0
+  cache-matches: 0
+])
+AT_CLEANUP
+
+AT_SETUP([ovn -- unit test -- lflow-cache negative tests])
+AT_CHECK([ovstest test-lflow-cache lflow_cache_negative], [0], [])
+AT_CLEANUP
diff --git a/tests/ovn.at b/tests/ovn.at
index 4a75acc..0e114cf 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -22235,6 +22235,74 @@  OVN_CLEANUP([hv1])
 
 AT_CLEANUP
 
+AT_SETUP([ovn -- lflow cache operations])
+ovn_start
+net_add n1
+sim_add hv1
+
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+as hv1
+ovs-vsctl -- add-port br-int hv1-vif1 \
+    -- set interface hv1-vif1 external-ids:iface-id=lsp1 \
+    -- add-port br-int hv1-vif2 \
+    -- set interface hv1-vif2 external-ids:iface-id=lsp2
+
+ovn-nbctl ls-add ls1 \
+    -- lsp-add ls1 lsp1 \
+    -- lsp-add ls1 lsp2 \
+    -- pg-add pg1 lsp1 lsp2 \
+    -- create Address_Set name=as1 addresses=\"10.0.0.1\",\"10.0.0.2\"
+check ovn-nbctl --wait=hv sync
+wait_for_ports_up lsp1 lsp2
+
+get_cache_count () {
+    local cache_name=$1
+    as hv1 ovn-appctl -t ovn-controller lflow-cache/show-stats | grep ${cache_name} | awk '{ print $3 }'
+}
+
+AS_BOX([Check matches caching])
+conj_id_cnt=$(get_cache_count cache-conj-id)
+expr_cnt=$(get_cache_count cache-expr)
+matches_cnt=$(get_cache_count cache-matches)
+
+check ovn-nbctl acl-add ls1 from-lport 1 '1' drop
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([test "$conj_id_cnt" = "$(get_cache_count cache-conj-id)"], [0], [])
+AT_CHECK([test "$expr_cnt" = "$(get_cache_count cache-expr)"], [0], [])
+AT_CHECK([test "$(($matches_cnt + 1))" = "$(get_cache_count cache-matches)"], [0], [])
+
+AS_BOX([Check expr caching for is_chassis_resident() matches])
+conj_id_cnt=$(get_cache_count cache-conj-id)
+expr_cnt=$(get_cache_count cache-expr)
+matches_cnt=$(get_cache_count cache-matches)
+
+check ovn-nbctl acl-add ls1 from-lport 1 'is_chassis_resident("lsp1")' drop
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([test "$conj_id_cnt" = "$(get_cache_count cache-conj-id)"], [0], [])
+AT_CHECK([test "$(($expr_cnt + 1))" = "$(get_cache_count cache-expr)"], [0], [])
+AT_CHECK([test "$matches_cnt" = "$(get_cache_count cache-matches)"], [0], [])
+
+AS_BOX([Check conj-id caching for conjunctive port group/address set matches])
+conj_id_cnt=$(get_cache_count cache-conj-id)
+expr_cnt=$(get_cache_count cache-expr)
+matches_cnt=$(get_cache_count cache-matches)
+
+check ovn-nbctl acl-add ls1 from-lport 1 'inport == @pg1 && outport == @pg1 && is_chassis_resident("lsp1")' drop
+check ovn-nbctl acl-add ls1 from-lport 1 'ip4.src == $as1 && ip4.dst == $as1 && is_chassis_resident("lsp1")' drop
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([test "$(($conj_id_cnt + 2))" = "$(get_cache_count cache-conj-id)"], [0], [])
+AT_CHECK([test "$expr_cnt" = "$(get_cache_count cache-expr)"], [0], [])
+AT_CHECK([test "$matches_cnt" = "$(get_cache_count cache-matches)"], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+
 AT_SETUP([ovn -- Delete Port_Binding and OVS port Incremental Processing])
 ovn_start
 
diff --git a/tests/test-utils.c b/tests/test-utils.c
new file mode 100644
index 0000000..6a3b198
--- /dev/null
+++ b/tests/test-utils.c
@@ -0,0 +1,49 @@ 
+/* Copyright (c) 2021, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "test-utils.h"
+
+#include "util.h"
+
+bool
+test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
+                     const char *descr, unsigned int *result)
+{
+    if (index >= ctx->argc) {
+        fprintf(stderr, "Missing %s argument\n", descr);
+        return false;
+    }
+
+    const char *arg = ctx->argv[index];
+    if (!str_to_uint(arg, 10, result)) {
+        fprintf(stderr, "Invalid %s: %s\n", descr, arg);
+        return false;
+    }
+    return true;
+}
+
+const char *
+test_read_value(struct ovs_cmdl_context *ctx, unsigned int index,
+                const char *descr)
+{
+    if (index >= ctx->argc) {
+        fprintf(stderr, "Missing %s argument\n", descr);
+        return NULL;
+    }
+
+    return ctx->argv[index];
+}
diff --git a/tests/test-utils.h b/tests/test-utils.h
new file mode 100644
index 0000000..721032f
--- /dev/null
+++ b/tests/test-utils.h
@@ -0,0 +1,26 @@ 
+/* Copyright (c) 2021, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H 1
+
+#include "ovstest.h"
+
+bool test_read_uint_value(struct ovs_cmdl_context *ctx, unsigned int index,
+                          const char *descr, unsigned int *result);
+const char *test_read_value(struct ovs_cmdl_context *ctx, unsigned int index,
+                            const char *descr);
+
+#endif /* tests/test-utils.h */
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 6253d11..35908ad 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -27,6 +27,7 @@  m4_include([tests/ovn.at])
 m4_include([tests/ovn-performance.at])
 m4_include([tests/ovn-northd.at])
 m4_include([tests/ovn-nbctl.at])
+m4_include([tests/ovn-lflow-cache.at])
 m4_include([tests/ovn-ofctrl-seqno.at])
 m4_include([tests/ovn-sbctl.at])
 m4_include([tests/ovn-ic-nbctl.at])