[ovs-dev] [RFC PATCH ovn 4/6] lib: Add infrastructure for plugging providers

Frode Nordahl frode.nordahl at gmail.com
Thu Aug 5 14:50:11 UTC 2021


This module contains the infrastructure for registering and
instantiating plugging classes which may be hosted inside or
outside the core OVN repository.  The data structures and functions
for interacting with these plugging classes also live here.

Signed-off-by: Frode Nordahl <frode.nordahl at canonical.com>
---
 lib/automake.mk     |   6 +-
 lib/plug-provider.h | 106 ++++++++++++++
 lib/plug-test.c     | 129 +++++++++++++++++
 lib/plug.c          | 342 ++++++++++++++++++++++++++++++++++++++++++++
 lib/plug.h          | 101 +++++++++++++
 5 files changed, 683 insertions(+), 1 deletion(-)
 create mode 100644 lib/plug-provider.h
 create mode 100644 lib/plug-test.c
 create mode 100644 lib/plug.c
 create mode 100644 lib/plug.h

diff --git a/lib/automake.mk b/lib/automake.mk
index d82908b92..272e48b4d 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -34,7 +34,11 @@ lib_libovn_la_SOURCES = \
 	lib/lb.h \
 	lib/stopwatch-names.h \
 	lib/ovsport.h \
-	lib/ovsport.c
+	lib/ovsport.c \
+	lib/plug-provider.h \
+	lib/plug.h \
+	lib/plug.c \
+	lib/plug-test.c
 nodist_lib_libovn_la_SOURCES = \
 	lib/ovn-dirs.c \
 	lib/ovn-nb-idl.c \
diff --git a/lib/plug-provider.h b/lib/plug-provider.h
new file mode 100644
index 000000000..f39637a5b
--- /dev/null
+++ b/lib/plug-provider.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2021 Canonical
+ *
+ * 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 PLUG_PROVIDER_H
+#define PLUG_PROVIDER_H 1
+
+/* Provider interface to pluggers.  A plugger implementation performs lookup
+ * and/or initialization of ports, typically representor ports, using generic
+ * non-blocking hardware interfaces.  This allows the ovn-controller to, upon
+ * the CMS's request, create ports and interfaces in the chassis's Open vSwitch
+ * instances (also known as vif plugging).
+ */
+
+#include <stdbool.h>
+
+#include "plug.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct plug {
+    const struct plug_class *plug_class;
+};
+
+struct plug_class {
+    /* Type of plugger in this class. */
+    const char *type;
+
+    /* Interface options this plugger will maintain.  This set is used
+     * to know which items to remove when maintaining the database record. */
+    struct sset *maintained_iface_options;
+
+    /* Called when the plug provider is registered, typically at program
+     * startup.
+     *
+     * This function may be set to null if a plug class needs no
+     * initialization at registration time. */
+    int (*init)(void);
+
+    /* Called when the plug provider is unregistered, typically at program
+     * exit.
+     *
+     * This function may be set to null if a plug class needs no
+     * de-initialization at unregister time.*/
+    int (*destroy)(void);
+
+    /* Creates or returns a reference to an existing plug class instance.
+     *
+     * If successful, stores a pointer to the plug instance in '*plugp' */
+    int (*open)(const struct plug_class *class, struct plug **plugp);
+
+    /* Closes plug class instance and frees associated memory. */
+    int (*close)(struct plug *plug);
+
+    /* Performs periodic work needed by plugger, if any is necessary.  Returns
+     * true if something changed, false otherwise. */
+    bool (*run)(struct plug *plug);
+
+    /* Pass plug_port_ctx_in to plug implementation to prepare for port
+     * creation/update.
+     *
+     * The plug implemantation can perform lookup or any per port
+     * initialization and should fill plug_port_ctx_out with data required for
+     * port/interface creation.  The plug implementation should return true if
+     * it wants the caller to create/update a port/interface, false otherwise.
+     *
+     * Data in the plug_port_ctx_out struct is owned by the plugging library,
+     * and a call must be made to the plug_port_ctx_destroy callback to free
+     * up any allocations when done with port creation/update.
+     */
+    bool (*plug_port_prepare)(const struct plug_port_ctx_in *,
+                              struct plug_port_ctx_out *);
+
+    /* Notify plugging library that port update is done. */
+    void (*plug_port_finish)(const struct plug_port_ctx_in *,
+                             struct plug_port_ctx_out *);
+
+    /* Free any allocations made by the plug_port_prepare callback. */
+    void (*plug_port_ctx_destroy)(const struct plug_port_ctx_in *,
+                                  struct plug_port_ctx_out *);
+};
+
+extern const struct plug_class plug_test_class;
+#if 0 // replace with ifdef HAVE_XXX once hooked up with build system
+extern const struct plug_class plug_representor_class;
+#endif
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* plug-provider.h */
diff --git a/lib/plug-test.c b/lib/plug-test.c
new file mode 100644
index 000000000..bacbad685
--- /dev/null
+++ b/lib/plug-test.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2021 Canonical
+ *
+ * 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 "plug-provider.h"
+#include "plug.h"
+
+#include <stdint.h>
+
+#include "ovsport.h"
+#include "openvswitch/vlog.h"
+#include "smap.h"
+#include "sset.h"
+
+VLOG_DEFINE_THIS_MODULE(plug_test);
+
+struct plug_test {
+    struct plug plug;
+};
+
+static struct sset plug_test_maintained_iface_options;
+
+static int
+plug_test_init(void)
+{
+    sset_init(&plug_test_maintained_iface_options);
+    sset_add(&plug_test_maintained_iface_options, "dpdk-devargs");
+
+    return 0;
+}
+
+static int
+plug_test_destroy(void)
+{
+    sset_destroy(&plug_test_maintained_iface_options);
+
+    return 0;
+}
+
+static int
+plug_test_open(const struct plug_class *class, struct plug **plugp)
+{
+    struct plug_test *plug;
+
+    plug = xmalloc(sizeof *plug);
+    plug->plug.plug_class = class;
+    *plugp = &plug->plug;
+
+    VLOG_INFO("plug_test_open(%p)", plug);
+    return 0;
+}
+
+static int
+plug_test_close(struct plug *plug)
+{
+    VLOG_INFO("plug_test_close(%p)", plug);
+    free(plug);
+
+    return 0;
+}
+
+static bool
+plug_test_run(struct plug *plug)
+{
+    VLOG_INFO("plug_test_run(%p)", plug);
+
+    return false;
+}
+
+static bool
+plug_test_port_prepare(const struct plug_port_ctx_in *ctx_in,
+                       struct plug_port_ctx_out *ctx_out)
+{
+    VLOG_INFO("plug_test_port_prepare: %s", ctx_in->lport_name);
+    if (ctx_in->op_type == PLUG_OP_CREATE)
+    {
+        ctx_out->name = strdup("test");
+        ctx_out->type = strdup("internal");
+        ctx_out->iface_options = xmalloc(sizeof *ctx_out->iface_options);
+        smap_init(ctx_out->iface_options);
+    }
+
+    return true;
+}
+
+static void
+plug_test_port_finish(const struct plug_port_ctx_in *ctx_in,
+                      struct plug_port_ctx_out *ctx_out OVS_UNUSED)
+{
+    VLOG_INFO("plug_test_port_finish: %s", ctx_in->lport_name);
+}
+
+static void
+plug_test_port_ctx_destroy(const struct plug_port_ctx_in *ctx_in,
+                           struct plug_port_ctx_out *ctx_out)
+{
+    VLOG_INFO("plug_test_port_ctx_destroy: %s", ctx_in->lport_name);
+    ovs_assert(ctx_in->op_type == PLUG_OP_CREATE);
+    free(ctx_out->name);
+    free(ctx_out->type);
+    smap_destroy(ctx_out->iface_options);
+    free(ctx_out->iface_options);
+}
+
+const struct plug_class plug_test_class = {
+    .type = "test",
+    .maintained_iface_options = &plug_test_maintained_iface_options,
+    .init = plug_test_init,
+    .destroy = plug_test_destroy,
+    .open = plug_test_open,
+    .close = plug_test_close,
+    .run = plug_test_run,
+    .plug_port_prepare = plug_test_port_prepare,
+    .plug_port_finish = plug_test_port_finish,
+    .plug_port_ctx_destroy = plug_test_port_ctx_destroy,
+};
diff --git a/lib/plug.c b/lib/plug.c
new file mode 100644
index 000000000..4a8650385
--- /dev/null
+++ b/lib/plug.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2021 Canonical
+ *
+ * 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 "plug-provider.h"
+#include "plug.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "ovsport.h"
+#include "openvswitch/vlog.h"
+#include "openvswitch/shash.h"
+#include "smap.h"
+#include "sset.h"
+
+VLOG_DEFINE_THIS_MODULE(plug);
+
+static const struct plug_class *base_plug_classes[] = {
+    &plug_test_class,
+#if 0 // replace with ifdef HAVE_XXX once hooked up with build system
+    &plug_representor_class,
+#endif
+};
+
+struct registered_plug_class {
+    const struct plug_class *plug_class;
+    int refcount;
+};
+static struct shash plug_classes = SHASH_INITIALIZER(&plug_classes);
+static struct shash plug_instances = SHASH_INITIALIZER(&plug_instances);
+
+/* Protects 'plug_classes', including the refcount. */
+static struct ovs_mutex plug_classes_mutex = OVS_MUTEX_INITIALIZER;
+/* Protects 'plug_instances' */
+static struct ovs_mutex plug_instances_mutex = OVS_MUTEX_INITIALIZER;
+
+/* Initialize the the plug infrastructure by registering known plug classes */
+static void
+plug_initialize(void)
+{
+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+
+    if (ovsthread_once_start(&once)) {
+        for (int i = 0; i < ARRAY_SIZE(base_plug_classes); i++) {
+            plug_register_provider(base_plug_classes[i]);
+        }
+    }
+
+    ovsthread_once_done(&once);
+}
+
+static int
+plug_register_provider__(const struct plug_class *new_class)
+{
+    struct registered_plug_class *rc;
+    int error;
+
+    if (shash_find(&plug_classes, new_class->type)) {
+        VLOG_WARN("attempted to register duplicate plug provider: %s",
+                  new_class->type);
+        return EEXIST;
+    }
+
+    error = new_class->init ? new_class->init() : 0;
+    if (error) {
+        VLOG_WARN("failed to initialize %s plug class: %s",
+                  new_class->type, ovs_strerror(error));
+        return error;
+    }
+
+    rc = xmalloc(sizeof *rc);
+    rc->plug_class = new_class;
+    rc->refcount = 0;
+
+    shash_add(&plug_classes, new_class->type, rc);
+
+    return 0;
+}
+
+/* Register the new plug provider referred to in 'new_class' and perform any
+ * class level initialization as specified in its plug_class. */
+int
+plug_register_provider(const struct plug_class *new_class)
+{
+    int error;
+
+    ovs_mutex_lock(&plug_classes_mutex);
+    error = plug_register_provider__(new_class);
+    ovs_mutex_unlock(&plug_classes_mutex);
+
+    return error;
+}
+
+static int
+plug_unregister_provider__(const char *type)
+{
+    int error;
+    struct shash_node *node;
+    struct registered_plug_class *rc;
+
+    node = shash_find(&plug_classes, type);
+    if (!node) {
+        return EINVAL;
+    }
+
+    rc = node->data;
+    if (rc->refcount) {
+        VLOG_WARN("attempted to unregister in use plug provider: %s", type);
+        return EBUSY;
+    }
+
+    error = rc->plug_class->destroy ? rc->plug_class->destroy() : 0;
+    if (error) {
+        VLOG_WARN("failed to destroy %s plug class: %s",
+                  rc->plug_class->type, ovs_strerror(error));
+        return error;
+    }
+
+    shash_delete(&plug_classes, node);
+    free(rc);
+
+    return 0;
+}
+
+/* Unregister the plug provider identified by 'type' and perform any class
+ * level de-initialization as specified in its plug_class. */
+int
+plug_unregister_provider(const char *type)
+{
+    int error;
+
+    plug_initialize();
+
+    ovs_mutex_lock(&plug_classes_mutex);
+    error = plug_unregister_provider__(type);
+    ovs_mutex_unlock(&plug_classes_mutex);
+
+    return error;
+}
+
+static void
+plug_class_unref(struct registered_plug_class *rc)
+{
+    ovs_mutex_lock(&plug_classes_mutex);
+    ovs_assert(rc->refcount);
+    rc->refcount--;
+    ovs_mutex_unlock(&plug_classes_mutex);
+}
+
+static struct registered_plug_class *
+plug_class_lookup(const char *type)
+{
+    struct registered_plug_class *rc;
+
+    ovs_mutex_lock(&plug_classes_mutex);
+    rc = shash_find_data(&plug_classes, type);
+    if (rc) {
+        rc->refcount++;
+    }
+    ovs_mutex_unlock(&plug_classes_mutex);
+
+    return rc;
+}
+
+static int
+plug_open__(const char *type, struct plug **plugp)
+{
+    struct plug *plug = NULL;
+    int error;
+    struct registered_plug_class *rc;
+
+    plug_initialize();
+    rc = plug_class_lookup(type);
+    if (!rc) {
+        VLOG_WARN("unable to open plug provider of unknown type: %s", type);
+        error = EINVAL;
+        goto out;
+    }
+
+    error = rc->plug_class->open(rc->plug_class, &plug);
+    if (error) {
+        plug_class_unref(rc);
+    }
+
+out:
+    *plugp = error ? NULL: plug;
+    return error;
+}
+
+/* Create, or retrieve the already created instance of plug class from a 
+ * previous call to plug_open, identified by 'type' and store a reference to it
+ * in '*plugp'.
+ *
+ * The plug implementation will perform any initialization and allocations it
+ * needs, and the plug infrastructure will store a reference to it.  Subsequent
+ * calls to this function with the same 'type' parameter will return the same
+ * object, until the instance is removed with a call to plug_close. */
+int
+plug_open(const char *type, struct plug **plugp)
+{
+    struct plug *instance = shash_find_data(&plug_instances, type);
+    int error;
+
+    if (instance) {
+        *plugp = instance;
+        return 0;
+    }
+
+    error = plug_open__(type, plugp);
+    if (error) {
+        return error;
+    }
+
+    ovs_mutex_lock(&plug_instances_mutex);
+    shash_add(&plug_instances, type, *plugp);
+    ovs_mutex_unlock(&plug_instances_mutex);
+
+    return 0;
+}
+
+/* Close the plug class instance previously created by a call to 'plug_open'.
+ *
+ * The plug implementation will perform any destruction of its data and the
+ * plug infrastructure will remove its references to it. */
+void
+plug_close(struct plug *plug)
+{
+    if (plug) {
+        ovs_mutex_lock(&plug_instances_mutex);
+        shash_find_and_delete(&plug_instances, plug->plug_class->type);
+        ovs_mutex_unlock(&plug_instances_mutex);
+
+        struct registered_plug_class *rc;
+        rc = shash_find_data(&plug_classes, plug->plug_class->type);
+        rc->plug_class->close(plug);
+        plug_class_unref(rc);
+    }
+}
+
+/* Close any previously instantiated plug classes and unregister the plug
+ * providers. */
+void
+plug_destroy_all(void)
+{
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE(node, next, &plug_instances) {
+        struct plug *plug = node->data;
+        plug_close(plug);
+    }
+    SHASH_FOR_EACH_SAFE(node, next, &plug_classes) {
+        struct registered_plug_class *rc = node->data;
+        plug_unregister_provider(rc->plug_class->type);
+    }
+}
+
+/* Iterate over previously instantiated plug classes and call their 'run'
+ * function if defined.
+ *
+ * If any of the instances report they have changed something this function
+ * will return 'true', otherwise it will return 'false'. */
+bool
+plug_run_instances(void)
+{
+    struct shash_node *node;
+    bool something_changed = false;
+
+    ovs_mutex_lock(&plug_instances_mutex);
+    SHASH_FOR_EACH(node, &plug_instances) {
+        struct plug *instance = node->data;
+        if (instance->plug_class->run(instance)) {
+            something_changed = true;
+        }
+    }
+    ovs_mutex_unlock(&plug_instances_mutex);
+
+    return something_changed;
+}
+
+/* Get the class level 'maintained_iface_options' set. */
+struct sset *
+plug_class_get_maintained_iface_options(struct plug *plug)
+{
+    return plug->plug_class->maintained_iface_options;
+}
+
+/* Prepare the logical port as identified by 'ctx_in' for port creation, update
+ * or removal as specified by 'ctx_in->op_type'.
+ *
+ * When 'ctx_in->op_type' is PLUG_OP_CREATE the plug implementation must fill
+ * 'ctx_out' with data to apply to the interface record maintained by OVN on
+ * its behalf.
+ *
+ * When 'ctx_in_op_type' is PLUG_OP_REMOVE 'ctx_out' should be set to NULL and
+ * the plug implementation must not attempt to use 'ctx_out'.
+ *
+ * The data in 'ctx_out' is owned by the plug implementation, and a call must
+ * be made to plug_port_ctx_destroy when done with it. */
+bool
+plug_port_prepare(const struct plug *plug,
+                  const struct plug_port_ctx_in *ctx_in,
+                  struct plug_port_ctx_out *ctx_out)
+{
+    if (ctx_out) {
+        memset(ctx_out, 0, sizeof(*ctx_out));
+    }
+    return plug->plug_class->plug_port_prepare(ctx_in, ctx_out);
+}
+
+/* Notify the plug implementation that a port creation, update or removal has
+ * been completed */
+void
+plug_port_finish(const struct plug *plug,
+                 const struct plug_port_ctx_in *ctx_in,
+                 struct plug_port_ctx_out *ctx_out)
+{
+    plug->plug_class->plug_port_finish(ctx_in, ctx_out);
+}
+
+/* Free any data allocated to 'ctx_out' in a prevous call to
+ * plug_port_prepare. */
+void
+plug_port_ctx_destroy(const struct plug *plug,
+                      const struct plug_port_ctx_in *ctx_in,
+                      struct plug_port_ctx_out *ctx_out)
+{
+    plug->plug_class->plug_port_ctx_destroy(ctx_in, ctx_out);
+}
diff --git a/lib/plug.h b/lib/plug.h
new file mode 100644
index 000000000..7a92ee1c8
--- /dev/null
+++ b/lib/plug.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2021 Canonical
+ *
+ * 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 PLUG_H
+#define PLUG_H 1
+
+/*
+ * Plug, the plugging interface.  This module contains the infrastructure for
+ * registering and instantiating plugging classes which may be hosted inside
+ * or outside the core OVN repository.  The data structures and functions for
+ * interacting with these plugging classes also live here.
+ */
+
+#include "smap.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+struct plug;
+struct plug_class;
+struct ovsdb_idl_txn;
+struct ovsrec_bridge;
+
+enum plug_op_type {
+    PLUG_OP_CREATE = 1, /* Port is created or updated */
+    PLUG_OP_REMOVE,     /* Port is removed from this chassis */
+};
+
+struct plug_port_ctx_in {
+    /* Operation being performed */
+    enum plug_op_type op_type;
+
+    /* Whether the chassis uses DPDK */
+    bool use_dpdk;
+
+    /* Name of logical port, can be useful for plugging library to track any
+     * per port resource initialization. */
+    const char *lport_name;
+
+    /* Logical port options, while OVN will forward the contents verbatim from
+     * the Southbound database, the convention is for the plugging library to
+     * only make decisions based on the plug-* options. */
+    const struct smap *lport_options;
+
+    /* When OVN knows about an existing interface record associated with this
+     * lport, these will be filled in with information about it. */
+    const char *iface_name;
+    const char *iface_type;
+    const struct smap *iface_options;
+};
+
+struct plug_port_ctx_out {
+    /* The name to use for port and interface record. */
+    char *name;
+
+    /* Type of interface to create. */
+    char *type;
+
+    /* Options to set on the interface record. */
+    struct smap *iface_options;
+};
+
+
+int plug_register_provider(const struct plug_class *);
+int plug_unregister_provider(const char *type);
+void plug_destroy_all(void);
+int plug_open(const char *type, struct plug **);
+void plug_close(struct plug *);
+bool plug_run_instances(void);
+
+struct sset * plug_class_get_maintained_iface_options(struct plug *);
+
+bool plug_port_prepare(const struct plug *,
+                       const struct plug_port_ctx_in *,
+                       struct plug_port_ctx_out *);
+void plug_port_finish(const struct plug *,
+                      const struct plug_port_ctx_in *,
+                      struct plug_port_ctx_out *);
+void plug_port_ctx_destroy(const struct plug *,
+                           const struct plug_port_ctx_in *,
+                           struct plug_port_ctx_out *);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif /* plug.h */
-- 
2.31.1



More information about the dev mailing list