On 30 Jul 2026, at 19:49, Aaron Conole wrote:
> This includes a test netdev offload and a suite of unit tests to
> ensure functionality. To facilitate the testing, some special
> offload APIs are added that force offload to true. It is expected
> that these are not called unless within a testing environment.
>
> The dummy provider is automatically registered when the "dummy" dpif
> offload class is active (hw-offload=true with a dummy datapath),
> providing an end-to-end integration path through the dpif-offload
> infrastructure.
>
> The integration test covers both directions: a forward packet on p1
> triggers conn_add (with p1's netdev as netdev_fwd_in), and a reply
> packet on p2 triggers conn_established (with p2's netdev as
> netdev_rev_in), verifying that the input netdev is correctly
> propagated through the full conntrack offload path in each direction.
Thanks for the updated patch Aaron. I think we could still
do a bit better at actually integrating ct-offload-dummy
with dpif-offload-dummy. It would also help removing the
changes in test-conntrack.c and do actual real dummy-dp
tests for these. See more details in the comments below.
Also, we should be able to see if a conntrack entry is
offloaded when doing 'ovs-appctl dpctl/dump-conntrack',
and maybe also to which provider.
Cheers,
Eelco
[...]
> +/* Counters are plain ints that can be read from any thread; this mutex
> + * protects both the list and the counters for consistency. */
> +static struct ovs_mutex dummy_mutex = OVS_MUTEX_INITIALIZER;
> +
> +/* List of offloaded connections, protected by dummy_mutex. */
> +static struct ovs_list dummy_conns OVS_GUARDED_BY(dummy_mutex)
> + = OVS_LIST_INITIALIZER(&dummy_conns);
> +
> +static unsigned int n_added = 0;
> +static unsigned int n_deleted = 0;
> +static unsigned int n_updated = 0;
> +static unsigned int n_established = 0;
I do like the alignment style, but OVS does not use it for
variable declarations, so not sure if we should use it here.
Also, per my other comments about not using the test app
but using the real netdev-dummy interface, these counters
should probably be coverage counters instead.
> +/* Lookup must be called with dummy_mutex held. */
> +static struct ct_dummy_entry *
> +dummy_find__(const struct conn *conn)
> + OVS_REQUIRES(dummy_mutex)
> +{
> + struct ct_dummy_entry *e;
> +
> + LIST_FOR_EACH (e, list_node, &dummy_conns) {
> + if (e->conn == conn) {
> + return e;
> + }
> + }
> + return NULL;
> +}
> +
> +static bool
> +dummy_can_offload(const struct ct_offload_ctx *ctx OVS_UNUSED)
> +{
It is not clear to me how this will integrate with
dpif-offload-dummy. For example, here we should check if
the input port is a dummy netdev, i.e. serviced by the
offload provider, similar to what the dpif offload provider
does in its can_offload callback. If not, we return false.
This way it can also be verified in unit tests with a coverage
counter.
> + return true;
> +}
> +
> +static int
> +dummy_conn_add(const struct ct_offload_ctx *ctx)
> +{
> + struct ct_dummy_entry *e = xmalloc(sizeof *e);
> +
Should we do a check to see if the connection already exists?
See also above on integration and port check.
Add an assert(ctx->key/ctx->conn) to safeguards the below,
and catch implementation problems? Maybe the same in other
API callbacks.
> + e->conn = ctx->conn;
> + e->netdev_fwd_in = ctx->netdev_in;
> + e->netdev_rev_in = NULL;
> +
> + ovs_mutex_lock(&dummy_mutex);
> + ovs_list_push_back(&dummy_conns, &e->list_node);
> + n_added++;
> + ovs_mutex_unlock(&dummy_mutex);
> +
> + VLOG_DBG("ct_offload_dummy: conn add: conn=%p, netdev_fwd_in=%p, "
> + "zone=%"PRIu16" proto=%"PRIu8,
> + ctx->conn, ctx->netdev_in,
> + ctx->key->zone, ctx->key->nw_proto);
Should we print the netdev name instead of the pointer?
The pointer address changes every run, making it hard to
match in tests. Something like netdev_get_name(ctx->netdev_in)
would be more useful, with a NULL guard.
> + return 0;
> +}
> +
> +static void
> +dummy_conn_del(const struct ct_offload_ctx *ctx)
> +{
> + ovs_mutex_lock(&dummy_mutex);
> + struct ct_dummy_entry *e = dummy_find__(ctx->conn);
> +
> + if (e) {
> + ovs_list_remove(&e->list_node);
> + n_deleted++;
> + free(e);
> + }
> + ovs_mutex_unlock(&dummy_mutex);
> +
> + VLOG_DBG("ct_offload_dummy: conn del: conn=%p", ctx->conn);
> +}
> +
> +static bool
> +dummy_conn_established(const struct ct_offload_ctx *ctx)
> +{
> + ovs_mutex_lock(&dummy_mutex);
> + struct ct_dummy_entry *e = dummy_find__(ctx->conn);
> + bool established = false;
> +
> + if (e && !e->netdev_rev_in) {
> + e->netdev_rev_in = ctx->netdev_in;
> + n_established++;
> + established = true;
> + VLOG_DBG("ct_offload_dummy: conn established: conn=%p "
> + "netdev_fwd_in=%p netdev_rev_in=%p",
> + ctx->conn, e->netdev_fwd_in, e->netdev_rev_in);
Same as earlier on port name.
> + }
> + ovs_mutex_unlock(&dummy_mutex);
> + return established;
> +}
> +
> +static long long
> +dummy_conn_update(const struct ct_offload_ctx *ctx)
> +{
> + ovs_mutex_lock(&dummy_mutex);
> + struct ct_dummy_entry *e = dummy_find__(ctx->conn);
> +
> + if (!e) {
> + ovs_mutex_unlock(&dummy_mutex);
> + return 0;
> + }
> +
> + n_updated++;
> + ovs_mutex_unlock(&dummy_mutex);
> +
> + VLOG_DBG("ct_offload_dummy: conn update: conn=%p", ctx->conn);
> + return time_msec();
> +}
> +
> +static void
> +dummy_flush(void)
> +{
> + ovs_mutex_lock(&dummy_mutex);
> + struct ct_dummy_entry *e;
New line, or my preference, move it above the mutex.
> + LIST_FOR_EACH_POP (e, list_node, &dummy_conns) {
> + n_deleted++;
> + free(e);
> + }
> + ovs_mutex_unlock(&dummy_mutex);
> +}
> +
> +/* Provider class. */
> +
> +const struct ct_offload_class ct_offload_dummy_class = {
> + .name = "dummy",
> + .init = NULL,
> + .batch_submit = NULL,
I think we should also have a batch_submit implementation
here so the batch provider path gets tested. See also my
comment on the backend only supporting batch mode.
> + .conn_add = dummy_conn_add,
> + .conn_del = dummy_conn_del,
> + .conn_update = dummy_conn_update,
> + .conn_established = dummy_conn_established,
> + .can_offload = dummy_can_offload,
> + .flush = dummy_flush,
> +};
[...]
> diff --git a/lib/ct-offload-dummy.h b/lib/ct-offload-dummy.h
> new file mode 100644
> index 0000000000..0fd8e2985a
> --- /dev/null
> +++ b/lib/ct-offload-dummy.h
> @@ -0,0 +1,66 @@
> +/*
> + * Copyright (c) 2026 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 CT_OFFLOAD_DUMMY_H
> +#define CT_OFFLOAD_DUMMY_H 1
> +
> +/* Dummy CT offload provider
> + * =========================
> + *
> + * A software-only implementation of the ct_offload_class interface used for
> + * unit testing. It records every conn_ add/del/update/established call
> + * and exposes inspection helpers so tests can verify that the correct
> + * hooks are reached without requiring any hardware.
> + *
> + * Typical usage:
> + *
> + * ct_offload_init_for_tests(); // allocate per-connection private slot
> + * ct_offload_force_enable(true); // bypass hardware-offload gate
> + * ct_offload_dummy_register(); // activate the provider
> + * conntrack_execute(...); // exercises conn_add
> + * ovs_assert(ct_offload_dummy_n_added() == 1);
> + * conntrack_flush(...); // exercises conn_del
> + * ovs_assert(ct_offload_dummy_n_deleted() == 1);
> + * ct_offload_dummy_unregister(); // tear down after test
> + * ct_offload_force_enable(false);
> + */
> +
> +#include <stdbool.h>
> +
> +struct conn;
> +
> +/* Register (or unregister) the dummy provider. */
> +void ct_offload_dummy_register(void);
> +void ct_offload_dummy_unregister(void);
> +
> +/* Counters. Initialized to zero and can be reset. */
> +unsigned int ct_offload_dummy_n_added(void);
> +unsigned int ct_offload_dummy_n_deleted(void);
> +unsigned int ct_offload_dummy_n_updated(void);
> +unsigned int ct_offload_dummy_n_established(void);
> +
> +/* Reset all counters without changing registered state. */
> +void ct_offload_dummy_reset_counters(void);
> +
> +/* Returns true if 'conn' is currently tracked by the dummy (was added but
> + * not yet deleted or flushed). */
> +bool ct_offload_dummy_contains(const struct conn *conn);
> +
> +/* Returns true if 'conn' has been seen in both directions (forward netdev
> + * recorded at conn_add, reply netdev recorded at conn_established). */
> +bool ct_offload_dummy_is_bidirectional(const struct conn *conn);
Should we use the same terminology as the ct-offload API,
i.e., 'is_established'?
> +
> +#endif /* CT_OFFLOAD_DUMMY_H */
> diff --git a/lib/ct-offload.c b/lib/ct-offload.c
> index 8c9c98e805..65c8036426 100644
> --- a/lib/ct-offload.c
> +++ b/lib/ct-offload.c
> @@ -22,6 +22,7 @@
>
> #include "conntrack.h"
> #include "conntrack-private.h"
> +#include "ct-offload-dummy.h"
> #include "dpif-offload.h"
> #include "ovs-thread.h"
> #include "util.h"
> @@ -33,9 +34,13 @@
> VLOG_DEFINE_THIS_MODULE(ct_offload);
>
> /* Built-in CT offload provider classes. Only those whose name matches a
> - * registered dpif offload class will be activated by
> ct_offload_module_init().
> - * Populated by downstream patches as concrete providers are added. */
> -static const struct ct_offload_class *base_ct_offload_classes[] OVS_UNUSED =
> {
> + * registered dpif offload class will be activated by
> + * ct_offload_module_init(). */
> +static const struct ct_offload_class *base_ct_offload_classes[] = {
> + /* Dummy provider: activated whenever the "dummy" dpif offload class is
> + * registered (hw-offload=true with a dummy datapath). Also used
> directly
> + * by unit tests via ct_offload_dummy_register(). */
This is not correct. The dummy provider is activated when
the dpif offload class is registered, not when it is
actually in use. Since all compiled-in dpif offload
classes are registered at startup, this results in the
dummy provider always being activated when hw-offload=true.
I do not think this is a good thing. It should be possible
to dynamically enable the specific conntrack plugin by the
dpif-offload provider. There are known cases where the
ct-offload should be disabled for a provider.
I guess for this to work we need an init and deinit (or
init/open/close) function for the ct-offload provider, similar
to what we have for the dpif-offload-provider.
> + &ct_offload_dummy_class,
> };
>
> /* Private slot for storing per-connection offload state. */
> @@ -156,8 +161,13 @@ void
> ct_offload_module_init(void)
> {
> ct_offload_alloc_private_slot();
> - /* No built-in providers yet; base_ct_offload_classes[] is populated as
> - * concrete providers are added in downstream patches. */
> + for (size_t i = 0; i < ARRAY_SIZE(base_ct_offload_classes); i++) {
> + const struct ct_offload_class *class = base_ct_offload_classes[i];
> +
> + if (dpif_offload_class_is_registered(class->name)) {
> + ct_offload_register(class);
> + }
> + }
> }
>
> /* ct_offload_init_for_tests() - allocate the internal private slot for
> tests.
> @@ -170,6 +180,15 @@ ct_offload_init_for_tests(void)
> ct_offload_alloc_private_slot();
> }
>
> +/* Used only in testing to bypass the hardware-offload gate. */
> +static bool ct_offload_forced = false;
> +
> +void
> +ct_offload_force_enable(bool value)
> +{
> + ct_offload_forced = value;
> +}
> +
Do we really need this? Can we not build these unit tests using the dummy
datapath instead?
> /* ct_offload_enabled() - returns true when hardware offload is active.
> *
> * Delegates to dpif_offload_enabled() so CT offload shares the same global
> @@ -177,7 +196,7 @@ ct_offload_init_for_tests(void)
> bool
> ct_offload_enabled(void)
> {
> - return dpif_offload_enabled();
> + return dpif_offload_enabled() || ct_offload_forced;
> }
>
> /* ct_offload_set_global_cfg() - configure CT offload from OVSDB.
> diff --git a/lib/ct-offload.h b/lib/ct-offload.h
> index 1496fc2b7e..1c2a63e06e 100644
> --- a/lib/ct-offload.h
> +++ b/lib/ct-offload.h
> @@ -91,6 +91,12 @@ struct ct_offload_class {
> void (*flush)(void);
> };
>
> +/* Dummy (software-only) CT offload provider, always compiled in.
> + * Registered automatically when the "dummy" dpif offload class is active
> + * (e.g. hw-offload=true with a dummy datapath), and available directly for
> + * unit tests via ct_offload_dummy_register() in ct-offload-dummy.h. */
> +extern const struct ct_offload_class ct_offload_dummy_class;
See earlier comment. Global registration is fine, but the
init (or an init/open/close approach like the dpif-offload
provider uses has my preferences) should be called by the
dpif-offload-provider, as it should control whether the CT
offload provider is active or not.
> +
> /* Register/unregister a provider. Must be called at module init, before
> * any connections are created. conn_add, conn_del, and can_offload must
> * be non-NULL. */
> @@ -112,6 +118,10 @@ void ct_offload_set_global_cfg(const struct
> ovsrec_open_vswitch *);
> * dpif_offload_enabled()). */
> bool ct_offload_enabled(void);
>
> +/* Used for testing. Forces an additional parameter for the offload enable
> + * check. Set to 'true' to always enable the offloads. */
> +void ct_offload_force_enable(bool);
> +
> /* Per-connection offload API that dispatches to all registered providers.
> * conn_add, conn_del, and conn_established require conn->lock to be held by
> * the caller; conn_update, can_offload, and flush do not. */
> diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c
> index 878276a94b..24d9fb913e 100644
> --- a/lib/dpif-offload-dummy.c
> +++ b/lib/dpif-offload-dummy.c
> @@ -649,14 +649,15 @@ dummy_offload_are_all_actions_supported(const struct
> dpif_offload *offload_,
> const struct nlattr *nla;
> size_t left;
>
> - /* Can we fully offload this flow? For now, only output actions are
> - * supported, and only to dummy-pmd netdevs where the egress port differs
> - * from the ingress port. The latter restriction ensures that the
> partial
> - * offload test cases pass.
> + /* Can we fully offload this flow? Output actions are supported for
> + * dummy-pmd netdevs where the egress port differs from the ingress port.
> + * CT actions are also accepted: the connection lifecycle is handled by
> the
> + * ct-offload provider (ct_offload_dummy_class when the dummy backend is
> + * active). Recirc is not yet supported at the hardware offload layer.
> *
> - * The reason for supporting only dummy-pmd netdevs as output targets is
> - * that they provide full protection when calling netdev_send() from any
> - * thread, via a netdev-level mutex. */
> + * The reason for restricting output to dummy-pmd netdevs is that they
> + * provide full protection when calling netdev_send() from any thread,
> + * via a netdev-level mutex. */
> NL_ATTR_FOR_EACH (nla, left, actions, actions_len) {
> enum ovs_action_attr action = nl_attr_type(nla);
>
> @@ -685,6 +686,11 @@ dummy_offload_are_all_actions_supported(const struct
> dpif_offload *offload_,
> break;
> }
>
> + case OVS_ACTION_ATTR_CT:
> + /* Handled by the ct-offload provider; accept without inspecting
> + * the nested attributes. */
> + break;
> +
> case OVS_ACTION_ATTR_UNSPEC:
> case OVS_ACTION_ATTR_USERSPACE:
> case OVS_ACTION_ATTR_SET:
> @@ -696,7 +702,6 @@ dummy_offload_are_all_actions_supported(const struct
> dpif_offload *offload_,
> case OVS_ACTION_ATTR_PUSH_MPLS:
> case OVS_ACTION_ATTR_POP_MPLS:
> case OVS_ACTION_ATTR_SET_MASKED:
> - case OVS_ACTION_ATTR_CT:
> case OVS_ACTION_ATTR_TRUNC:
> case OVS_ACTION_ATTR_PUSH_ETH:
> case OVS_ACTION_ATTR_POP_ETH:
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index 14f238e622..7a30f41425 100644
> --- a/tests/dpif-netdev.at
> +++ b/tests/dpif-netdev.at
> @@ -64,6 +64,18 @@ filter_hw_packet_netdev_dummy () {
> | sort | uniq
> }
>
> +filter_ct_offload_dummy_conn_add () {
> + grep 'ct_offload_dummy.*conn add:' | sed 's/.*|DBG|//' | sort | uniq
> +}
> +
> +filter_ct_offload_dummy_conn_del () {
> + grep 'ct_offload_dummy.*conn del:' | sed 's/.*|DBG|//' | sort | uniq
> +}
> +
> +filter_ct_offload_dummy_conn_established () {
> + grep 'ct_offload_dummy.*conn established:' | sed 's/.*|DBG|//' | sort |
> uniq
> +}
> +
> filter_flow_dump () {
> grep 'flow_dump ' | sed '
> s/.*flow_dump //
> @@ -71,6 +83,18 @@ filter_flow_dump () {
> ' | sort | uniq
> }
>
> +strip_ct_dump_flows () {
> + sed '
> + /^flow-dump from/d
> + s/ufid:[-0-9a-f]*, //
> + s/, packets:[0-9]*//
> + s/, bytes:[0-9]*//
> + s/, used:[^ ,]*//
> + s/[^,]*(0\/0),//g
> + s/, dp-extra-info.*//
> + s/^[[:space:]]*//' | sort
> +}
> +
> strip_metadata () {
> sed 's/metadata=0x[0-9a-f]*/metadata=0x0/'
> }
> @@ -3774,3 +3798,184 @@ OVS_VSWITCHD_STOP(["dnl
> /.*failed to put.*$/d
> /.*failed to flow_del.*$/d"])
> AT_CLEANUP
> +
> +dnl Test that the CT offload dummy provider receives conn_add,
> conn_established,
> +dnl and conn_del callbacks when packets traverse a conntrack commit flow in
> both
> +dnl directions on a dummy datapath with hw-offload enabled. Also verifies
> that
> +dnl the input netdev is correctly propagated in each direction:
> netdev_fwd_in at
> +dnl conn_add, netdev_rev_in at conn_established.
Lots of (AI) comments for obvious things; maybe we should reduce them?
> +AT_SETUP([dpif-netdev - conntrack offload dummy])
> +AT_KEYWORDS([conntrack offload])
> +OVS_VSWITCHD_START(
> + [add-port br0 p1 -- \
> + set interface p1 type=dummy ofport_request=1 \
> + options:pstream=punix:$OVS_RUNDIR/p1.sock \
> + options:ifindex=1100 -- \
> + add-port br0 p2 -- \
> + set interface p2 type=dummy ofport_request=2 \
> + options:pstream=punix:$OVS_RUNDIR/p2.sock \
> + options:ifindex=1101 -- \
> + set bridge br0 datapath-type=dummy \
> + other-config:datapath-id=1234 fail-mode=secure], [], [],
> [])
> +
> +dnl Enable debug logging for the dpif offload and CT offload dummy modules so
> +dnl the test can detect hook calls via log grep.
> +AT_CHECK([ovs-appctl vlog/set dpif_offload_dummy:file:dbg
> ct_offload_dummy:file:dbg])
> +
> +dnl Enable hardware offload - this registers the "dummy" dpif offload class
> +dnl and automatically activates the CT offload dummy provider.
> +AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:hw-offload=true])
> +OVS_WAIT_UNTIL([grep "Flow HW offload is enabled" ovs-vswitchd.log])
> +
> +dnl Add conntrack flows for both directions:
> +dnl table 0: untracked forward (p1->p2) -> ct(commit) recirculate to table
> 1
> +dnl table 1: tracked forward (p1->p2) -> output on p2
> +dnl table 0: untracked reply (p2->p1) -> ct() recirculate to table 1
> +dnl table 1: tracked reply (p2->p1) -> output on p1
> +AT_CHECK([ovs-ofctl add-flow br0 \
> +
> 'table=0,priority=100,in_port=p1,ip,ct_state=-trk,actions=ct(commit,table=1)'])
> +AT_CHECK([ovs-ofctl add-flow br0 \
> + 'table=1,priority=100,in_port=p1,ip,ct_state=+trk,actions=output:p2'])
> +AT_CHECK([ovs-ofctl add-flow br0 \
> + 'table=0,priority=100,in_port=p2,ip,ct_state=-trk,actions=ct(table=1)'])
> +AT_CHECK([ovs-ofctl add-flow br0 \
> + 'table=1,priority=100,in_port=p2,ip,ct_state=+trk,actions=output:p1'])
> +
> +dnl Compose and inject a UDP packet on p1. The first packet misses the
> +dnl datapath, causes an upcall, executes ct(commit) to create a conntrack
> +dnl entry, and triggers the ct_offload_dummy conn_add callback with p1's
> netdev
> +dnl as netdev_fwd_in.
> +flow_s="eth_src=50:54:00:00:00:01,eth_dst=50:54:00:00:00:02,udp,ip_src=10.0.0.1,ip_dst=10.0.0.2,ip_frag=no,udp_src=1000,udp_dst=2000"
> +pkt=$(ovs-ofctl compose-packet --bare "${flow_s}")
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 "${pkt}"])
> +
> +dnl Wait for the CT offload dummy conn_add hook to fire.
> +OVS_WAIT_UNTIL([grep 'ct_offload_dummy.*conn add:' ovs-vswitchd.log])
We should probably check for the ingress netdev also,
see earlier code comment.
> +
> +dnl Verify exactly one connection was added.
> +AT_CHECK([filter_ct_offload_dummy_conn_add < ovs-vswitchd.log | wc -l | tr
> -d ' '],
> + [0], [1
> +])
> +
> +dnl Verify the forward-direction input netdev was propagated (non-NULL).
> +AT_CHECK([filter_ct_offload_dummy_conn_add < ovs-vswitchd.log \
> + | grep -cv 'netdev_fwd_in=(nil)'], [0], [1
> +])
The specific port check mentioned above would obsolete this.
> +
> +dnl Inject a reply packet on p2 (src/dst swapped). This causes an upcall,
> +dnl processes the reply through ct(), finds the existing connection in the
> +dnl reply direction (CS_ESTABLISHED | CS_REPLY_DIR), and triggers the
> +dnl ct_offload_dummy conn_established callback with p2's netdev as
> netdev_rev_in.
> +reply_flow_s="eth_src=50:54:00:00:00:02,eth_dst=50:54:00:00:00:01,udp,ip_src=10.0.0.2,ip_dst=10.0.0.1,ip_frag=no,udp_src=2000,udp_dst=1000"
> +reply_pkt=$(ovs-ofctl compose-packet --bare "${reply_flow_s}")
> +AT_CHECK([ovs-appctl netdev-dummy/receive p2 "${reply_pkt}"])
> +
> +dnl Wait for the CT offload dummy conn_established hook to fire.
> +OVS_WAIT_UNTIL([grep 'ct_offload_dummy.*conn established:' ovs-vswitchd.log])
> +
> +dnl Verify exactly one connection was established.
> +AT_CHECK([filter_ct_offload_dummy_conn_established < ovs-vswitchd.log | wc
> -l | tr -d ' '],
> + [0], [1
> +])
> +
> +dnl Verify the reply-direction input netdev was propagated (non-NULL).
> +AT_CHECK([filter_ct_offload_dummy_conn_established < ovs-vswitchd.log \
> + | grep -cv 'netdev_rev_in=(nil)'], [0], [1
> +])
> +
> +dnl Flush all conntrack entries - conn_clean is called for every tracked
> +dnl connection, which invokes ct_offload_conn_del on each registered
> provider.
> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
> +
> +dnl Wait for the CT offload dummy conn_del hook to fire.
> +OVS_WAIT_UNTIL([grep 'ct_offload_dummy.*conn del:' ovs-vswitchd.log])
> +
> +dnl Verify exactly one connection was deleted.
> +AT_CHECK([filter_ct_offload_dummy_conn_del < ovs-vswitchd.log | wc -l | tr
> -d ' '],
> + [0], [1
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
> +AT_SETUP([dpif-netdev - conntrack offload dummy dump-flows])
Does this need to be a separate test? Could be part of the previous one?
> +AT_KEYWORDS([conntrack offload])
> +OVS_VSWITCHD_START(
> + [add-port br0 p1 -- \
[...]
> diff --git a/tests/library.at b/tests/library.at
> index df4b8d2886..53e97375b3 100644
> --- a/tests/library.at
> +++ b/tests/library.at
I think all these tests should be real unit tests using the
dummy datapath. This will exercise the full integration
path and avoid the extra test-only APIs and checks in the
production code.
> @@ -320,3 +320,39 @@ AT_KEYWORDS([conntrack])
> AT_CHECK([ovstest test-conntrack private-multi-slot], [0], [.
> ])
> AT_CLEANUP
> +
> +AT_SETUP([conntrack offload dummy - conn add hook])
> +AT_KEYWORDS([conntrack offload])
> +AT_CHECK([ovstest test-conntrack offload-conn-add], [0], [.
> +])
> +AT_CLEANUP
[...]
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev