diff mbox series

[ovs-dev,v3,04/13] ofproto: Add ofproto-dpif-lsample.

Message ID 20240712170618.1903024-5-amorenoz@redhat.com
State Superseded
Headers show
Series Introduce local sampling with NXAST_SAMPLE action. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation fail test: fail

Commit Message

Adrián Moreno July 12, 2024, 5:06 p.m. UTC
Add a new resource in ofproto-dpif and the corresponding API in
ofproto_provider.h to represent and local sampling configuration.

Acked-by: Eelco Chaudron <echaudro@redhat.com>
Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 ofproto/automake.mk            |   2 +
 ofproto/ofproto-dpif-lsample.c | 187 +++++++++++++++++++++++++++++++++
 ofproto/ofproto-dpif-lsample.h |  35 ++++++
 ofproto/ofproto-dpif.c         |  38 +++++++
 ofproto/ofproto-dpif.h         |   1 +
 ofproto/ofproto-provider.h     |   9 ++
 ofproto/ofproto.c              |  12 +++
 ofproto/ofproto.h              |   8 ++
 8 files changed, 292 insertions(+)
 create mode 100644 ofproto/ofproto-dpif-lsample.c
 create mode 100644 ofproto/ofproto-dpif-lsample.h

Comments

Ilya Maximets July 12, 2024, 10:40 p.m. UTC | #1
On 7/12/24 19:06, Adrian Moreno wrote:
> Add a new resource in ofproto-dpif and the corresponding API in
> ofproto_provider.h to represent and local sampling configuration.
> 
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---
>  ofproto/automake.mk            |   2 +
>  ofproto/ofproto-dpif-lsample.c | 187 +++++++++++++++++++++++++++++++++
>  ofproto/ofproto-dpif-lsample.h |  35 ++++++
>  ofproto/ofproto-dpif.c         |  38 +++++++
>  ofproto/ofproto-dpif.h         |   1 +
>  ofproto/ofproto-provider.h     |   9 ++
>  ofproto/ofproto.c              |  12 +++
>  ofproto/ofproto.h              |   8 ++
>  8 files changed, 292 insertions(+)
>  create mode 100644 ofproto/ofproto-dpif-lsample.c
>  create mode 100644 ofproto/ofproto-dpif-lsample.h

A couple nits inline.

> 
> diff --git a/ofproto/automake.mk b/ofproto/automake.mk
> index 7c08b563b..cb1361b8a 100644
> --- a/ofproto/automake.mk
> +++ b/ofproto/automake.mk
> @@ -30,6 +30,8 @@ ofproto_libofproto_la_SOURCES = \
>  	ofproto/ofproto-dpif.h \
>  	ofproto/ofproto-dpif-ipfix.c \
>  	ofproto/ofproto-dpif-ipfix.h \
> +	ofproto/ofproto-dpif-lsample.c \
> +	ofproto/ofproto-dpif-lsample.h \
>  	ofproto/ofproto-dpif-mirror.c \
>  	ofproto/ofproto-dpif-mirror.h \
>  	ofproto/ofproto-dpif-monitor.c \
> diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
> new file mode 100644
> index 000000000..a2b2e8059
> --- /dev/null
> +++ b/ofproto/ofproto-dpif-lsample.c
> @@ -0,0 +1,187 @@
> +/*
> + * Copyright (c) 2024 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 "ofproto-dpif-lsample.h"
> +
> +#include "cmap.h"
> +#include "hash.h"
> +#include "ofproto.h"
> +#include "openvswitch/thread.h"
> +
> +/* Dpif local sampling.
> + *
> + * Thread safety: dpif_lsample allows lockless concurrent reads of local
> + * sampling exporters as long as the following restrictions are met:
> + *   1) While the last reference is being dropped, i.e: a thread is calling
> + *      "dpif_lsample_unref" on the last reference, other threads cannot call
> + *      "dpif_lsample_ref".
> + *   2) Threads do not quiese while holding references to internal
> + *      lsample_exporter objects.
> + */
> +
> +struct dpif_lsample {
> +    struct cmap exporters;          /* Contains lsample_exporter_node instances
> +                                     * indexed by collector_set_id. */
> +    struct ovs_mutex mutex;         /* Protects concurrent insertion/deletion
> +                                     * of exporters. */
> +

This empty line not needed here.

> +    struct ovs_refcount ref_cnt;    /* Controls references to this instance. */
> +};
> +
> +struct lsample_exporter {
> +    struct ofproto_lsample_options options;
> +};
> +
> +struct lsample_exporter_node {
> +    struct cmap_node node;              /* In dpif_lsample->exporters. */
> +    struct lsample_exporter exporter;
> +};
> +
> +static void
> +dpif_lsample_delete_exporter(struct dpif_lsample *lsample,
> +                             struct lsample_exporter_node *node)
> +{
> +    ovs_mutex_lock(&lsample->mutex);
> +    cmap_remove(&lsample->exporters, &node->node,
> +                hash_int(node->exporter.options.collector_set_id, 0));
> +    ovs_mutex_unlock(&lsample->mutex);
> +
> +    ovsrcu_postpone(free, node);
> +}
> +
> +/* Adds an exporter with the provided options which are copied. */
> +static struct lsample_exporter_node *
> +dpif_lsample_add_exporter(struct dpif_lsample *lsample,
> +                          const struct ofproto_lsample_options *options)
> +{
> +    struct lsample_exporter_node *node;
> +
> +    node = xzalloc(sizeof *node);
> +    node->exporter.options = *options;
> +
> +    ovs_mutex_lock(&lsample->mutex);
> +    cmap_insert(&lsample->exporters, &node->node,
> +                hash_int(options->collector_set_id, 0));
> +    ovs_mutex_unlock(&lsample->mutex);
> +
> +    return node;
> +}
> +
> +static struct lsample_exporter_node *
> +dpif_lsample_find_exporter_node(const struct dpif_lsample *lsample,
> +                                const uint32_t collector_set_id)
> +{
> +    struct lsample_exporter_node *node;
> +
> +    CMAP_FOR_EACH_WITH_HASH (node, node,
> +                            hash_int(collector_set_id, 0),
> +                            &lsample->exporters) {

Indentation is not correct.  And can actually be written in just 2 lines.

> +        if (node->exporter.options.collector_set_id == collector_set_id) {
> +            return node;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +/* Sets the lsample configuration and returns true if the configuration
> + * has changed. */
> +bool
> +dpif_lsample_set_options(struct dpif_lsample *lsample,
> +                         const struct ofproto_lsample_options *options,
> +                         size_t n_options)
> +{
> +    const struct ofproto_lsample_options *opt;
> +    struct lsample_exporter_node *node;
> +    bool changed = false;
> +    int i;
> +
> +    for (i = 0; i < n_options; i++) {
> +        opt = &options[i];
> +        node = dpif_lsample_find_exporter_node(lsample,
> +                                               opt->collector_set_id);
> +        if (!node) {
> +            dpif_lsample_add_exporter(lsample, opt);
> +            changed = true;
> +        } else if (memcmp(&node->exporter.options, opt, sizeof(*opt))) {

Don't parenthesize the argument of the sizeof.

> +            dpif_lsample_delete_exporter(lsample, node);
> +            dpif_lsample_add_exporter(lsample, opt);
> +            changed = true;
> +        }
> +    }
> +
> +    /* Delete exporters that have been removed. */
> +    CMAP_FOR_EACH (node, node, &lsample->exporters) {
> +        for (i = 0; i < n_options; i++) {
> +            if (node->exporter.options.collector_set_id
> +                == options[i].collector_set_id) {
> +                break;
> +            }
> +        }
> +        if (i == n_options) {
> +            dpif_lsample_delete_exporter(lsample, node);
> +            changed = true;
> +        }
> +    }
> +
> +    return changed;
> +}
> +
> +struct dpif_lsample *
> +dpif_lsample_create(void)
> +{
> +    struct dpif_lsample *lsample;
> +
> +    lsample = xzalloc(sizeof *lsample);
> +    cmap_init(&lsample->exporters);
> +    ovs_mutex_init(&lsample->mutex);
> +    ovs_refcount_init(&lsample->ref_cnt);
> +
> +    return lsample;
> +}
> +
> +static void
> +dpif_lsample_destroy(struct dpif_lsample *lsample)
> +{
> +    if (lsample) {
> +        struct lsample_exporter_node *node;
> +
> +        CMAP_FOR_EACH (node, node, &lsample->exporters) {
> +            dpif_lsample_delete_exporter(lsample, node);
> +        }
> +        cmap_destroy(&lsample->exporters);
> +        free(lsample);
> +    }
> +}
> +
> +struct dpif_lsample *
> +dpif_lsample_ref(const struct dpif_lsample *lsample_)
> +{
> +    struct dpif_lsample *lsample = CONST_CAST(struct dpif_lsample *, lsample_);
> +
> +    if (lsample) {
> +        ovs_refcount_ref(&lsample->ref_cnt);
> +    }
> +    return lsample;
> +}
> +
> +void
> +dpif_lsample_unref(struct dpif_lsample *lsample)
> +{
> +    if (lsample && ovs_refcount_unref_relaxed(&lsample->ref_cnt) == 1) {
> +        dpif_lsample_destroy(lsample);
> +    }
> +}
> diff --git a/ofproto/ofproto-dpif-lsample.h b/ofproto/ofproto-dpif-lsample.h
> new file mode 100644
> index 000000000..a491c137d
> --- /dev/null
> +++ b/ofproto/ofproto-dpif-lsample.h
> @@ -0,0 +1,35 @@
> +/*
> + * Copyright (c) 2024 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 OFPROTO_DPIF_LSAMPLE_H
> +#define OFPROTO_DPIF_LSAMPLE_H 1
> +
> +#include <stdbool.h>
> +#include <stdlib.h>
> +
> +struct dpif_lsample;
> +struct ofproto_lsample_options;
> +
> +struct dpif_lsample *dpif_lsample_create(void);
> +
> +struct dpif_lsample *dpif_lsample_ref(const struct dpif_lsample *);
> +void dpif_lsample_unref(struct dpif_lsample *);
> +
> +bool dpif_lsample_set_options(struct dpif_lsample *,
> +                              const struct ofproto_lsample_options *,
> +                              size_t n_opts);
> +
> +#endif /* OFPROTO_DPIF_LSAMPLE_H */
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index c2d2fab6f..59b9a5252 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -50,6 +50,7 @@
>  #include "ofproto-dpif-sflow.h"
>  #include "ofproto-dpif-trace.h"
>  #include "ofproto-dpif-upcall.h"
> +#include "ofproto-dpif-lsample.h"
>  #include "ofproto-dpif-xlate.h"
>  #include "ofproto-dpif-xlate-cache.h"
>  #include "openvswitch/ofp-actions.h"
> @@ -1957,6 +1958,7 @@ destruct(struct ofproto *ofproto_, bool del)
>      netflow_unref(ofproto->netflow);
>      dpif_sflow_unref(ofproto->sflow);
>      dpif_ipfix_unref(ofproto->ipfix);
> +    dpif_lsample_unref(ofproto->lsample);
>      hmap_destroy(&ofproto->bundles);
>      mac_learning_unref(ofproto->ml);
>      mcast_snooping_unref(ofproto->ms);
> @@ -2516,6 +2518,41 @@ get_ipfix_stats(const struct ofproto *ofproto_,
>      return dpif_ipfix_get_stats(di, bridge_ipfix, replies);
>  }
>  
> +static int
> +set_local_sample(struct ofproto *ofproto_,
> +                 const struct ofproto_lsample_options *options,
> +                 size_t n_opts)
> +{
> +    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
> +    struct dpif_lsample *lsample = ofproto->lsample;
> +    bool changed = false;
> +
> +    if (!ofproto->backer->rt_support.psample) {
> +        return EOPNOTSUPP;
> +    }
> +
> +    if (n_opts && !lsample) {
> +        lsample = ofproto->lsample = dpif_lsample_create();
> +        changed = true;
> +    }
> +
> +    if (lsample) {
> +        if (!n_opts) {
> +            dpif_lsample_unref(lsample);
> +            lsample = ofproto->lsample = NULL;
> +            changed = true;
> +        } else if (dpif_lsample_set_options(lsample, options, n_opts)) {
> +            changed = true;
> +        }
> +    }
> +
> +    if (changed) {
> +        ofproto->backer->need_revalidate = REV_RECONFIGURE;
> +    }
> +
> +    return 0;
> +}
> +
>  static int
>  set_cfm(struct ofport *ofport_, const struct cfm_settings *s)
>  {
> @@ -7201,6 +7238,7 @@ const struct ofproto_class ofproto_dpif_class = {
>      set_sflow,
>      set_ipfix,
>      get_ipfix_stats,
> +    set_local_sample,
>      set_cfm,
>      cfm_status_changed,
>      get_cfm_status,
> diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
> index ea18ccedf..d7b7d5f8c 100644
> --- a/ofproto/ofproto-dpif.h
> +++ b/ofproto/ofproto-dpif.h
> @@ -331,6 +331,7 @@ struct ofproto_dpif {
>      struct netflow *netflow;
>      struct dpif_sflow *sflow;
>      struct dpif_ipfix *ipfix;
> +    struct dpif_lsample *lsample;
>      struct hmap bundles;        /* Contains "struct ofbundle"s. */
>      struct mac_learning *ml;
>      struct mcast_snooping *ms;
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index 83c509fcf..85991554c 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -1489,6 +1489,15 @@ struct ofproto_class {
>          bool bridge_ipfix, struct ovs_list *replies
>          );
>  
> +    /* Configures local sampling on 'ofproto' according to the options array
> +     * of 'options' which contains 'n_options' elements.
> +     *
> +     * EOPNOTSUPP as a return value indicates that 'ofproto' does not support
> +     * local sampling. */
> +    int (*set_local_sample)(struct ofproto *ofproto,
> +                            const struct ofproto_lsample_options *options,
> +                            size_t n_options);
> +
>      /* Configures connectivity fault management on 'ofport'.
>       *
>       * If 'cfm_settings' is nonnull, configures CFM according to its members.
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 21c6a1d82..8c1efe4bf 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -1000,6 +1000,18 @@ ofproto_get_datapath_cap(const char *datapath_type, struct smap *dp_cap)
>      }
>  }
>  
> +int ofproto_set_local_sample(struct ofproto *ofproto,
> +                             const struct ofproto_lsample_options *options,
> +                             size_t n_options)
> +{
> +    if (ofproto->ofproto_class->set_local_sample) {
> +        return ofproto->ofproto_class->set_local_sample(ofproto, options,
> +                                                        n_options);
> +    } else {
> +        return EOPNOTSUPP;
> +    }
> +}
> +
>  /* Connection tracking configuration. */
>  void
>  ofproto_ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
> diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
> index 1c07df275..f1ff80e52 100644
> --- a/ofproto/ofproto.h
> +++ b/ofproto/ofproto.h
> @@ -103,6 +103,11 @@ struct ofproto_ipfix_flow_exporter_options {
>      char *virtual_obs_id;
>  };
>  
> +struct ofproto_lsample_options {
> +    uint32_t collector_set_id;
> +    uint32_t group_id;
> +};
> +
>  struct ofproto_rstp_status {
>      bool enabled;               /* If false, ignore other members. */
>      rstp_identifier root_id;
> @@ -371,6 +376,9 @@ int ofproto_set_ipfix(struct ofproto *,
>                        const struct ofproto_ipfix_bridge_exporter_options *,
>                        const struct ofproto_ipfix_flow_exporter_options *,
>                        size_t);
> +int ofproto_set_local_sample(struct ofproto *ofproto,
> +                             const struct ofproto_lsample_options *,
> +                             size_t n_options);
>  void ofproto_set_flow_restore_wait(bool flow_restore_wait_db);
>  bool ofproto_get_flow_restore_wait(void);
>  int ofproto_set_stp(struct ofproto *, const struct ofproto_stp_settings *);
diff mbox series

Patch

diff --git a/ofproto/automake.mk b/ofproto/automake.mk
index 7c08b563b..cb1361b8a 100644
--- a/ofproto/automake.mk
+++ b/ofproto/automake.mk
@@ -30,6 +30,8 @@  ofproto_libofproto_la_SOURCES = \
 	ofproto/ofproto-dpif.h \
 	ofproto/ofproto-dpif-ipfix.c \
 	ofproto/ofproto-dpif-ipfix.h \
+	ofproto/ofproto-dpif-lsample.c \
+	ofproto/ofproto-dpif-lsample.h \
 	ofproto/ofproto-dpif-mirror.c \
 	ofproto/ofproto-dpif-mirror.h \
 	ofproto/ofproto-dpif-monitor.c \
diff --git a/ofproto/ofproto-dpif-lsample.c b/ofproto/ofproto-dpif-lsample.c
new file mode 100644
index 000000000..a2b2e8059
--- /dev/null
+++ b/ofproto/ofproto-dpif-lsample.c
@@ -0,0 +1,187 @@ 
+/*
+ * Copyright (c) 2024 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 "ofproto-dpif-lsample.h"
+
+#include "cmap.h"
+#include "hash.h"
+#include "ofproto.h"
+#include "openvswitch/thread.h"
+
+/* Dpif local sampling.
+ *
+ * Thread safety: dpif_lsample allows lockless concurrent reads of local
+ * sampling exporters as long as the following restrictions are met:
+ *   1) While the last reference is being dropped, i.e: a thread is calling
+ *      "dpif_lsample_unref" on the last reference, other threads cannot call
+ *      "dpif_lsample_ref".
+ *   2) Threads do not quiese while holding references to internal
+ *      lsample_exporter objects.
+ */
+
+struct dpif_lsample {
+    struct cmap exporters;          /* Contains lsample_exporter_node instances
+                                     * indexed by collector_set_id. */
+    struct ovs_mutex mutex;         /* Protects concurrent insertion/deletion
+                                     * of exporters. */
+
+    struct ovs_refcount ref_cnt;    /* Controls references to this instance. */
+};
+
+struct lsample_exporter {
+    struct ofproto_lsample_options options;
+};
+
+struct lsample_exporter_node {
+    struct cmap_node node;              /* In dpif_lsample->exporters. */
+    struct lsample_exporter exporter;
+};
+
+static void
+dpif_lsample_delete_exporter(struct dpif_lsample *lsample,
+                             struct lsample_exporter_node *node)
+{
+    ovs_mutex_lock(&lsample->mutex);
+    cmap_remove(&lsample->exporters, &node->node,
+                hash_int(node->exporter.options.collector_set_id, 0));
+    ovs_mutex_unlock(&lsample->mutex);
+
+    ovsrcu_postpone(free, node);
+}
+
+/* Adds an exporter with the provided options which are copied. */
+static struct lsample_exporter_node *
+dpif_lsample_add_exporter(struct dpif_lsample *lsample,
+                          const struct ofproto_lsample_options *options)
+{
+    struct lsample_exporter_node *node;
+
+    node = xzalloc(sizeof *node);
+    node->exporter.options = *options;
+
+    ovs_mutex_lock(&lsample->mutex);
+    cmap_insert(&lsample->exporters, &node->node,
+                hash_int(options->collector_set_id, 0));
+    ovs_mutex_unlock(&lsample->mutex);
+
+    return node;
+}
+
+static struct lsample_exporter_node *
+dpif_lsample_find_exporter_node(const struct dpif_lsample *lsample,
+                                const uint32_t collector_set_id)
+{
+    struct lsample_exporter_node *node;
+
+    CMAP_FOR_EACH_WITH_HASH (node, node,
+                            hash_int(collector_set_id, 0),
+                            &lsample->exporters) {
+        if (node->exporter.options.collector_set_id == collector_set_id) {
+            return node;
+        }
+    }
+    return NULL;
+}
+
+/* Sets the lsample configuration and returns true if the configuration
+ * has changed. */
+bool
+dpif_lsample_set_options(struct dpif_lsample *lsample,
+                         const struct ofproto_lsample_options *options,
+                         size_t n_options)
+{
+    const struct ofproto_lsample_options *opt;
+    struct lsample_exporter_node *node;
+    bool changed = false;
+    int i;
+
+    for (i = 0; i < n_options; i++) {
+        opt = &options[i];
+        node = dpif_lsample_find_exporter_node(lsample,
+                                               opt->collector_set_id);
+        if (!node) {
+            dpif_lsample_add_exporter(lsample, opt);
+            changed = true;
+        } else if (memcmp(&node->exporter.options, opt, sizeof(*opt))) {
+            dpif_lsample_delete_exporter(lsample, node);
+            dpif_lsample_add_exporter(lsample, opt);
+            changed = true;
+        }
+    }
+
+    /* Delete exporters that have been removed. */
+    CMAP_FOR_EACH (node, node, &lsample->exporters) {
+        for (i = 0; i < n_options; i++) {
+            if (node->exporter.options.collector_set_id
+                == options[i].collector_set_id) {
+                break;
+            }
+        }
+        if (i == n_options) {
+            dpif_lsample_delete_exporter(lsample, node);
+            changed = true;
+        }
+    }
+
+    return changed;
+}
+
+struct dpif_lsample *
+dpif_lsample_create(void)
+{
+    struct dpif_lsample *lsample;
+
+    lsample = xzalloc(sizeof *lsample);
+    cmap_init(&lsample->exporters);
+    ovs_mutex_init(&lsample->mutex);
+    ovs_refcount_init(&lsample->ref_cnt);
+
+    return lsample;
+}
+
+static void
+dpif_lsample_destroy(struct dpif_lsample *lsample)
+{
+    if (lsample) {
+        struct lsample_exporter_node *node;
+
+        CMAP_FOR_EACH (node, node, &lsample->exporters) {
+            dpif_lsample_delete_exporter(lsample, node);
+        }
+        cmap_destroy(&lsample->exporters);
+        free(lsample);
+    }
+}
+
+struct dpif_lsample *
+dpif_lsample_ref(const struct dpif_lsample *lsample_)
+{
+    struct dpif_lsample *lsample = CONST_CAST(struct dpif_lsample *, lsample_);
+
+    if (lsample) {
+        ovs_refcount_ref(&lsample->ref_cnt);
+    }
+    return lsample;
+}
+
+void
+dpif_lsample_unref(struct dpif_lsample *lsample)
+{
+    if (lsample && ovs_refcount_unref_relaxed(&lsample->ref_cnt) == 1) {
+        dpif_lsample_destroy(lsample);
+    }
+}
diff --git a/ofproto/ofproto-dpif-lsample.h b/ofproto/ofproto-dpif-lsample.h
new file mode 100644
index 000000000..a491c137d
--- /dev/null
+++ b/ofproto/ofproto-dpif-lsample.h
@@ -0,0 +1,35 @@ 
+/*
+ * Copyright (c) 2024 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 OFPROTO_DPIF_LSAMPLE_H
+#define OFPROTO_DPIF_LSAMPLE_H 1
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+struct dpif_lsample;
+struct ofproto_lsample_options;
+
+struct dpif_lsample *dpif_lsample_create(void);
+
+struct dpif_lsample *dpif_lsample_ref(const struct dpif_lsample *);
+void dpif_lsample_unref(struct dpif_lsample *);
+
+bool dpif_lsample_set_options(struct dpif_lsample *,
+                              const struct ofproto_lsample_options *,
+                              size_t n_opts);
+
+#endif /* OFPROTO_DPIF_LSAMPLE_H */
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index c2d2fab6f..59b9a5252 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -50,6 +50,7 @@ 
 #include "ofproto-dpif-sflow.h"
 #include "ofproto-dpif-trace.h"
 #include "ofproto-dpif-upcall.h"
+#include "ofproto-dpif-lsample.h"
 #include "ofproto-dpif-xlate.h"
 #include "ofproto-dpif-xlate-cache.h"
 #include "openvswitch/ofp-actions.h"
@@ -1957,6 +1958,7 @@  destruct(struct ofproto *ofproto_, bool del)
     netflow_unref(ofproto->netflow);
     dpif_sflow_unref(ofproto->sflow);
     dpif_ipfix_unref(ofproto->ipfix);
+    dpif_lsample_unref(ofproto->lsample);
     hmap_destroy(&ofproto->bundles);
     mac_learning_unref(ofproto->ml);
     mcast_snooping_unref(ofproto->ms);
@@ -2516,6 +2518,41 @@  get_ipfix_stats(const struct ofproto *ofproto_,
     return dpif_ipfix_get_stats(di, bridge_ipfix, replies);
 }
 
+static int
+set_local_sample(struct ofproto *ofproto_,
+                 const struct ofproto_lsample_options *options,
+                 size_t n_opts)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+    struct dpif_lsample *lsample = ofproto->lsample;
+    bool changed = false;
+
+    if (!ofproto->backer->rt_support.psample) {
+        return EOPNOTSUPP;
+    }
+
+    if (n_opts && !lsample) {
+        lsample = ofproto->lsample = dpif_lsample_create();
+        changed = true;
+    }
+
+    if (lsample) {
+        if (!n_opts) {
+            dpif_lsample_unref(lsample);
+            lsample = ofproto->lsample = NULL;
+            changed = true;
+        } else if (dpif_lsample_set_options(lsample, options, n_opts)) {
+            changed = true;
+        }
+    }
+
+    if (changed) {
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
+    }
+
+    return 0;
+}
+
 static int
 set_cfm(struct ofport *ofport_, const struct cfm_settings *s)
 {
@@ -7201,6 +7238,7 @@  const struct ofproto_class ofproto_dpif_class = {
     set_sflow,
     set_ipfix,
     get_ipfix_stats,
+    set_local_sample,
     set_cfm,
     cfm_status_changed,
     get_cfm_status,
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index ea18ccedf..d7b7d5f8c 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -331,6 +331,7 @@  struct ofproto_dpif {
     struct netflow *netflow;
     struct dpif_sflow *sflow;
     struct dpif_ipfix *ipfix;
+    struct dpif_lsample *lsample;
     struct hmap bundles;        /* Contains "struct ofbundle"s. */
     struct mac_learning *ml;
     struct mcast_snooping *ms;
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 83c509fcf..85991554c 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1489,6 +1489,15 @@  struct ofproto_class {
         bool bridge_ipfix, struct ovs_list *replies
         );
 
+    /* Configures local sampling on 'ofproto' according to the options array
+     * of 'options' which contains 'n_options' elements.
+     *
+     * EOPNOTSUPP as a return value indicates that 'ofproto' does not support
+     * local sampling. */
+    int (*set_local_sample)(struct ofproto *ofproto,
+                            const struct ofproto_lsample_options *options,
+                            size_t n_options);
+
     /* Configures connectivity fault management on 'ofport'.
      *
      * If 'cfm_settings' is nonnull, configures CFM according to its members.
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 21c6a1d82..8c1efe4bf 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -1000,6 +1000,18 @@  ofproto_get_datapath_cap(const char *datapath_type, struct smap *dp_cap)
     }
 }
 
+int ofproto_set_local_sample(struct ofproto *ofproto,
+                             const struct ofproto_lsample_options *options,
+                             size_t n_options)
+{
+    if (ofproto->ofproto_class->set_local_sample) {
+        return ofproto->ofproto_class->set_local_sample(ofproto, options,
+                                                        n_options);
+    } else {
+        return EOPNOTSUPP;
+    }
+}
+
 /* Connection tracking configuration. */
 void
 ofproto_ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 1c07df275..f1ff80e52 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -103,6 +103,11 @@  struct ofproto_ipfix_flow_exporter_options {
     char *virtual_obs_id;
 };
 
+struct ofproto_lsample_options {
+    uint32_t collector_set_id;
+    uint32_t group_id;
+};
+
 struct ofproto_rstp_status {
     bool enabled;               /* If false, ignore other members. */
     rstp_identifier root_id;
@@ -371,6 +376,9 @@  int ofproto_set_ipfix(struct ofproto *,
                       const struct ofproto_ipfix_bridge_exporter_options *,
                       const struct ofproto_ipfix_flow_exporter_options *,
                       size_t);
+int ofproto_set_local_sample(struct ofproto *ofproto,
+                             const struct ofproto_lsample_options *,
+                             size_t n_options);
 void ofproto_set_flow_restore_wait(bool flow_restore_wait_db);
 bool ofproto_get_flow_restore_wait(void);
 int ofproto_set_stp(struct ofproto *, const struct ofproto_stp_settings *);