[libvirt PATCH v2 09/16] nodedev: add an mdevctl thread
Erik Skultety
eskultet at redhat.com
Tue Aug 25 14:49:49 UTC 2020
On Tue, Aug 18, 2020 at 09:47:59AM -0500, Jonathon Jongsma wrote:
> We need to peridocally query mdevctl for changes to device definitions
> since an administrator can define new devices with mdevctl outside of
> libvirt.
>
> In order to do this, a new thread is created to handle the querying. In
> the future, mdevctl may add a way to signal device add / remove via
> events, but for now we resort to a bit of a workaround: monitoring the
> mdevctl config directories for changes to files. When a change is
> detected, we query mdevctl and update our device list.
>
> Signed-off-by: Jonathon Jongsma <jjongsma at redhat.com>
> ---
> src/node_device/node_device_udev.c | 182 +++++++++++++++++++++++++++++
> 1 file changed, 182 insertions(+)
>
> diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_device_udev.c
> index e06584a3dc..a85418e549 100644
> --- a/src/node_device/node_device_udev.c
> +++ b/src/node_device/node_device_udev.c
> @@ -41,6 +41,7 @@
> #include "virnetdev.h"
> #include "virmdev.h"
> #include "virutil.h"
> +#include <gio/gio.h>
>
> #include "configmake.h"
>
> @@ -66,6 +67,11 @@ struct _udevEventData {
> virCond threadCond;
> bool threadQuit;
> bool dataReady;
> +
> + virThread mdevctlThread;
> + virCond mdevctlCond;
> + bool mdevctlReady;
> + GList *mdevctl_monitors;
> };
>
> static virClassPtr udevEventDataClass;
> @@ -85,8 +91,10 @@ udevEventDataDispose(void *obj)
> udev = udev_monitor_get_udev(priv->udev_monitor);
> udev_monitor_unref(priv->udev_monitor);
> udev_unref(udev);
> + g_list_free_full(priv->mdevctl_monitors, g_object_unref);
>
> virCondDestroy(&priv->threadCond);
> + virCondDestroy(&priv->mdevctlCond);
> }
>
>
> @@ -117,6 +125,11 @@ udevEventDataNew(void)
> return NULL;
> }
>
> + if (virCondInit(&ret->mdevctlCond) < 0) {
> + virObjectUnref(ret);
> + return NULL;
> + }
> +
> ret->watch = -1;
> return ret;
> }
> @@ -1520,6 +1533,7 @@ nodeStateCleanup(void)
> virObjectLock(priv);
> priv->threadQuit = true;
> virCondSignal(&priv->threadCond);
> + virCondSignal(&priv->mdevctlCond);
> virObjectUnlock(priv);
> virThreadJoin(&priv->th);
> }
> @@ -1601,6 +1615,40 @@ udevEventMonitorSanityCheck(udevEventDataPtr priv,
> }
>
>
> +/* Thread to query mdevctl for the current state of persistent mediated device
> + * defintions when any changes are detected */
> +static
> +void mdevctlThread(void *opaque G_GNUC_UNUSED)
> +{
> + udevEventDataPtr priv = driver->privateData;
> +
> + while (1) {
> + virObjectLock(priv);
> + while (!priv->mdevctlReady && !priv->threadQuit) {
> + if (virCondWait(&priv->mdevctlCond, &priv->parent.lock)) {
> + virReportSystemError(errno, "%s",
> + _("handler failed to wait on condition"));
> + virObjectUnlock(priv);
> + return;
> + }
> + }
> +
> + if (priv->threadQuit) {
> + virObjectUnlock(priv);
> + return;
> + }
> +
> + virObjectUnlock(priv);
> +
> + mdevctlEnumerateDevices();
> +
> + virObjectLock(priv);
> + priv->mdevctlReady = false;
> + virObjectUnlock(priv);
> + }
> +}
> +
> +
> /**
> * udevEventHandleThread
> * @opaque: unused
> @@ -1708,6 +1756,69 @@ udevEventHandleCallback(int watch G_GNUC_UNUSED,
> }
>
>
> +static int timeout_id;
> +
> +static gboolean
> +signalMdevctlThread(void *user_data)
> +{
> + udevEventDataPtr priv = user_data;
> +
> + if (timeout_id) {
> + g_source_remove(timeout_id);
> + timeout_id = 0;
> + }
> +
> + virObjectLock(priv);
> + priv->mdevctlReady = true;
> + virCondSignal(&priv->mdevctlCond);
> + virObjectUnlock(priv);
> +
> + return G_SOURCE_REMOVE;
> +}
> +
> +
> +static void
> +mdevctlEventHandleCallback(GFileMonitor *monitor G_GNUC_UNUSED,
> + GFile *file,
> + GFile *other_file G_GNUC_UNUSED,
> + GFileMonitorEvent event_type,
> + gpointer user_data)
> +{
> + udevEventDataPtr priv = user_data;
> +
> + /* if a new directory appears, monitor that directory for changes */
> + if (g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL) ==
> + G_FILE_TYPE_DIRECTORY) {
> + GFileMonitor *mon;
> +
> + if ((mon = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, NULL))) {
> + g_signal_connect(mon, "changed",
> + G_CALLBACK(mdevctlEventHandleCallback),
> + user_data);
> + virObjectLock(priv);
> + priv->mdevctl_monitors = g_list_append(priv->mdevctl_monitors, mon);
> + virObjectUnlock(priv);
> + }
> + }
> +
> + /* Sometimes a single configuration change results in multiple notify
> + * events (e.g. several CHANGED events, or a CREATED and CHANGED event
Isn't this a glib bug that it sends multiple (even different) events for the
same change? Same for CHANGES_DONE_HINT, how come it may not arrive at the end
of all the changes?
Regards,
Erik
> + * followed by CHANGES_DONE_HINT). To avoid triggering the mdevctl thread
> + * multiple times for a single configuration change, try to coalesce these
> + * changes by waiting for the CHANGES_DONE_HINT event. As a fallback, add
> + * a timeout to trigger the signal if that event never comes */
> + if (event_type == G_FILE_MONITOR_EVENT_CREATED ||
> + event_type == G_FILE_MONITOR_EVENT_CHANGED) {
> + if (timeout_id)
> + g_source_remove(timeout_id);
> + timeout_id = g_timeout_add(100, signalMdevctlThread, priv);
> + return;
> + }
> +
> + signalMdevctlThread(priv);
> +}
> +
> +
> /* DMI is intel-compatible specific */
> #if defined(__x86_64__) || defined(__i386__) || defined(__amd64__)
> static void
> @@ -1858,6 +1969,58 @@ udevPCITranslateInit(bool privileged G_GNUC_UNUSED)
> }
>
>
> +/* Recursively monitors dir and any subdirectory for file changes and returns a
> + * GList of GFileMonitor objects */
> +static GList*
> +monitorDirRecursively(GFile *dir,
> + GCallback changed_cb,
> + gpointer user_data)
> +{
> + GList *monitors = NULL;
> + g_autoptr(GFileInfo) dirinfo = NULL;
> + g_autoptr(GError) error = NULL;
> + g_autoptr(GFileEnumerator) children = NULL;
> + GFileMonitor *mon;
> +
> + if (!(children = g_file_enumerate_children(dir, "standard::*",
> + G_FILE_QUERY_INFO_NONE, NULL, &error))) {
> + if (error->code == G_IO_ERROR_NOT_DIRECTORY)
> + return NULL;
> + goto bail;
> + }
> +
> + if (!(mon = g_file_monitor(dir, G_FILE_MONITOR_NONE, NULL, &error)))
> + goto bail;
> +
> + g_signal_connect(mon, "changed", changed_cb, user_data);
> + monitors = g_list_append(monitors, mon);
> +
> + while (true) {
> + GFileInfo *info;
> + GFile *child;
> + GList *child_monitors = NULL;
> +
> + if (!g_file_enumerator_iterate(children, &info, &child, NULL, &error))
> + goto bail;
> + if (!info)
> + break;
> +
> + child_monitors = monitorDirRecursively(child, changed_cb, user_data);
> + if (child_monitors)
> + monitors = g_list_concat(monitors, child_monitors);
> + }
> +
> + return monitors;
> +
> + bail:
> + if (error)
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Unable to monitor directory: %s"), error->message);
> +
> + g_list_free_full(monitors, g_object_unref);
> + return NULL;
> +}
> +
> static int
> nodeStateInitialize(bool privileged,
> const char *root,
> @@ -1867,6 +2030,8 @@ nodeStateInitialize(bool privileged,
> udevEventDataPtr priv = NULL;
> struct udev *udev = NULL;
> virThread enumThread;
> + g_autoptr(GError) error = NULL;
> + g_autoptr(GFile) mdevctlConfigDir = g_file_new_for_path("/etc/mdevctl.d");
>
> if (root != NULL) {
> virReportError(VIR_ERR_INVALID_ARG, "%s",
> @@ -1969,6 +2134,23 @@ nodeStateInitialize(bool privileged,
> if (priv->watch == -1)
> goto unlock;
>
> + if (virThreadCreateFull(&priv->mdevctlThread, true, mdevctlThread,
> + "mdevctl-event", false, NULL) < 0) {
> + virReportSystemError(errno, "%s",
> + _("failed to create mdevctl handler thread"));
> + goto unlock;
> + }
> +
> + /* mdevctl may add notification events in the future:
> + * https://github.com/mdevctl/mdevctl/issues/27. For now, fall back to
> + * monitoring the mdevctl configuration directory for changes.
> + * mdevctl configuration is stored in a directory tree within
> + * /etc/mdevctl.d/. There is a directory for each parent device, which
> + * contains a file defining each mediated device */
> + priv->mdevctl_monitors = monitorDirRecursively(mdevctlConfigDir,
> + G_CALLBACK(mdevctlEventHandleCallback),
> + priv);
> +
> virObjectUnlock(priv);
>
> /* Create a fictional 'computer' device to root the device tree. */
> --
> 2.26.2
>
More information about the libvir-list
mailing list